Skip to content

Model eBay's Buy Browse scope beside the Sell Inventory modules - #10983

Merged
briansrls merged 14 commits into
mainfrom
session/sunny-carp-475
Sep 11, 2026
Merged

briansrls merged 14 commits into
mainfrom
session/sunny-carp-475

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

First of two PRs for the buyer-agent pipeline (buyer-agent-pipeline, gunbc-private strategy.year_end_plan). This one covers exit-criterion part 1: the marketplace as a cited upstream, with the sheet's columns derivable from the API's real response shape. The pipeline itself (ListingObservation, the three-valued attribute sheet, PurchaseIntent) lands in gunbc-private and consumes this PR.

What lands

  • extdeps.ebay.browse: the Buy Browse scope of the existing eBay subject, added next to inventory.dag rather than as a second eBay module tree (DESIGN §3, external upstream decomposition).
    • Two operations: ebay.Browse.SearchItemSummaries and ebay.Browse.GetItem. Both are readonly and use application-token auth against the existing extdeps.ebay.oauth api_scope_root.
    • Response shapes come from eBay's published buy_browse_v1_oas3 contract (v1.20.4). Modeled fields are the ones a buyer adjudicates; the rest are listed per type in CoverageClosureEntry residuals, so nothing is dropped silently.
    • Condition IDs. The numeric condition-ID table is decoded onto the existing ConditionEnum: same concept, different wire. That makes it one table in both directions, not a second enum. Unlisted IDs decode to Absent, and MANUFACTURER_REFURBISHED has no ID, so encoding it refuses.
    • Search filters. The filter grammar is a typed coproduct.
      • price and priceCurrency render together, so a price without its currency can't be written.
      • An empty value set refuses.
      • A condition with no ID refuses the whole filter, because a dropped field would silently widen the search.
    • browse_contextual_location_header builds the X-EBAY-C-ENDUSERCTX header. Without it, calculated shipping isn't returned and a delivered price can't be formed.
    • browse_production_access records the upstream fact that production Buy access is an eBay Partner Network application with no guaranteed approval. The private pipeline consumes it as an intervention whose outcome can be a refusal.
  • extdeps.ebay.rate_limits: adds ebay_buy_browse_rate_limit, 5,000/day. product.capacity.quota derives its pool unchanged, so the cadence is derivable, not asserted. capacity_quota_witness now counts 11 scopes, up from 10.
  • extdeps.currency.currency: adds the ISO 4217 alphabetic code and its parse.
  • Published mock cases for both operations, and browse.dag enrolled in scope_carrier_paths.
  • test.claim.ebay_browse_witness: decode and round-trip controls, the full filter rendering, two filter refusals, the header against eBay's own example, and the rate-limit row.

Consumers of these declarations (DESIGN §3c)

  • Executing consumers in this PR: the 12 witnesses in test.claim.ebay_browse_witness. They run:
    • the condition-ID decode and encode folds;
    • the whole filter renderer, including both refusals;
    • the search-page admission, including all three refusals;
    • the contextualLocation header;
    • the ISO 4217 alphabetic code.
      The rate-limit row is consumed by product.capacity.quota through capacity_quota_witness's identity join.
  • Response types and browse_production_access: consumed by the private buyer pipeline in its companion change. It uses them for the Browse fold into listing evidence, the access standing, and the schedule's result-window check.
  • The scheduled executor that calls these operations is deliberately not built yet. Its trigger is approved production access and observed response bodies. Until then the operations are reached only through those folds. This is a declared deferral, not an unconsumed model.

Findings that change the downstream shape

  1. Search results carry no item aspects. ItemSummary has no localizedAspects, mpn or product. Rank, speed, capacity and MPN therefore cost one getItem per candidate, and the scan cadence has to be derived from candidate volume against 5,000/day.
  2. Delivered price is price plus shippingOptions[].shippingCost, evaluated at the buyer's contextualLocation.
  3. Production access is Limited Release, as described above. The scout pipeline refuses while the application is anything but approved. It never falls back to scraping.

Provenance and evidence standing

  • How the facts were read. developer.ebay.com answers HTTP 403 to our fetches.
    • The contract was read from a GitHub mirror of eBay's published OpenAPI file (hendt/ebay-api specs/buy_browse_v1_oas3.json, info.version 1.20.4).
    • Call limits, production access, the condition-ID table and the filter grammar came from web.archive.org captures of the cited eBay pages, taken 2026-05 and 2026-06.
    • Each fact's citation is the canonical eBay URL.
  • Not executed yet. This change has been checked by source inspection only. No session route executes .dag witnesses, so test.claim.ebay_browse_witness first runs in CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r

gunbc-ci-auto-heal and others added 2 commits September 10, 2026 23:45
extdeps.ebay.browse adds the buy-side scope of the existing eBay subject:
the search and getItem operations, their response shapes read from the
published buy_browse_v1_oas3 contract (v1.20.4), the numeric condition-ID
table decoded onto the existing ConditionEnum, a typed search-filter
grammar that cannot write a price without its currency, the
contextualLocation header delivered price depends on, and the upstream
fact that production Buy access is an eBay Partner Network application
with no guaranteed approval.

The 5,000/day Browse call limit joins extdeps.ebay.rate_limits, so
product.capacity.quota derives its pool with no second authority; the
ISO 4217 alphabetic code joins extdeps.currency.currency. The two new
operations get published mock cases and the module enrolls as a scope
carrier.

developer.ebay.com returns 403 to our fetches; the contract was read from
a mirror of eBay's published OpenAPI file and the prose facts from
web.archive.org captures of the cited pages (2026-05/06). Closed by
source inspection only here: no session route executes .dag witnesses,
so test.claim.ebay_browse_witness first executes in CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
…tity join

Review 63298 findings, all taken:
- BrowsePriceAtMost carries a MoneyAmountMicro, rendered through
  std.decimal fixed_point_wire at the filter boundary, instead of a
  string beside a typed currency.
- BrowseConvertedAmount types currency and convertedFromCurrency as
  CurrencyCode, matching the Sell scope's EbayAmount, so the one eBay
  money-on-the-wire fact no longer has a weaker second shape.
- parse_currency_alphabetic_code is removed: with the currency fields
  typed it had no production consumer.
- capacity_quota_witness joins the rate-limit roster against the
  published scope keys in both directions instead of a count literal.

Also fixes the four parse errors CI reported at f29da9a: annotations
before test fns attach to nothing, and a literal brace in a string
began interpolation. The search operation drops its category_ids input,
which no consumer supplies.

Executed locally with a gunbc built today (/cargo-target/release/gunbc):
all eleven Browse witnesses pass. A condition-table mutation reds
exactly the two witnesses that depend on the row, and removing the
buy_browse key from the expected set reds the quota join.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63298 in b06a8ca. All four findings were right.

  • Price as a string. BrowsePriceAtMost.max is now a MoneyAmountMicro. It renders to the filter's decimal through std.decimal fixed_point_wire, only at the wire.
  • Second amount shape. BrowseConvertedAmount.currency and convertedFromCurrency are now CurrencyCode, matching extdeps.ebay.inventory EbayAmount. The only Browse-specific addition left is the pre-conversion pair.
  • Dangling parse. parse_currency_alphabetic_code is removed. With the fields typed it had no production consumer.
  • Count literal. capacity_quota_witness no longer asserts length == 11. It joins the roster's keys against the published scope keys in both directions, and it keeps the key-uniqueness check.

The commit also fixes the parse errors CI reported at f29da9a: annotations in front of test fn attach to nothing, and a literal { inside a string began interpolation.

How this was checked. I ran it locally with a gunbc built today (/cargo-target/release/gunbc).

  • All eleven Browse witnesses pass.
  • Changing condition-table row 3000 to 3001 reds exactly the two witnesses that depend on that row.
  • Removing ebay-buy_browse from the expected scope set reds the quota join.

Pre-existing, not from this PR. test.claim.external_model_scope_witness carriers_declare_scope_on_disk is red on main as well. dag/extdeps/standards/rfc_8118.dag is a rostered carrier whose scope is named rfc_8118_model_scope, not extdeps_model_scope. The new dag/extdeps/ebay/browse.dag carrier declares the expected name.

…ge boundary

Review 63312: browse_search_limit_max and browse_search_result_window
were rows no fold read, and "offset must be a multiple of limit" was a
comment. browse_search_page_admission now refuses a limit outside 1..200,
an offset that is not a page boundary, and a page past the 10,000-item
window, each as its own typed arm, before a request is formed. The
witness covers the admitted page and all three refusals.

Also imports CoverageClosureFact beside CoverageClosureEntry, as every
sibling that writes Subject/JsonPending rows does. The module already
resolved without it (record-literal constructors bind by corpus-wide
uniqueness, and the witnesses executed), so this is conformance, not
a compile fix.

Executed locally with /cargo-target/release/gunbc: all twelve Browse
witnesses pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63312 in the commit after b06a8ca.

  • browse_search_result_window / browse_search_limit_max. Both are now read by browse_search_page_admission. It refuses three cases, each as its own arm, before any request is formed: a limit outside 1..200, an offset that isn't a page boundary, and a page past the 10,000-item window. That turns the "offset must be a multiple of limit" comment into a wall. witness_browse_search_page_admission_holds_the_published_bounds covers the admitted case and all three refusals.
  • browse_production_access / BrowseProductionAccess. The consumer is the buyer pipeline's access standing in the private overlay, which lands next. It derives "awaiting the Partner Network application" from this row and uses application_route as the intervention's subject. The public tree may not name private carriers (the private overlay's placement rule), so I can't cite the consuming module here. It is a declared frontier whose consumer arrives with the private change; it is not dangling. I've added a line to the row's annotation naming the consuming role.
  • CoverageClosureFact import. Confirmed that it resolved: record-literal constructors bind by corpus-wide uniqueness, and the Browse witnesses executed against this module locally. I've added the import anyway, because every sibling writes it and the divergence was unstated.

I ran the Browse witness suite locally again: all twelve pass.

— sent from sunny-carp-475

Review 63325: browse_production_access had no reader in the public
closure and no declared frontier. ebay_browse_frontier_rows now names it
and the two response types (BrowseItem, BrowseSearchPagedCollection),
each with a dissolution that names the capability rather than an
artifact. Approved access PLUS an observed response body folded on an
executing entry fires them; approval alone, a fixture body, or a witness
that the row exists does not. This follows the extdeps.backblaze.b2
frontier-row precedent. The witness checks the rows are well formed.

Executed locally: all thirteen Browse witnesses pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63325 in 77c7198. I kept the row rather than dropping it: production access is an upstream fact about this subject, and deleting it would push the fact down into the consumer. What was missing is the declared frontier, so ebay_browse_frontier_rows now names browse_production_access and the two response types that no executing entry reads yet (BrowseItem, BrowseSearchPagedCollection). This follows the extdeps.backblaze.b2 precedent. Each row's dissolution names the capability that retires it: an executing entry that folds an observed response body with approved access. Approval alone, a fixture body, or a witness that the row exists does not retire it. witness_browse_frontier_rows_are_well_formed checks the rows. I ran the Browse suite again locally and all 13 pass.

@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

On review 63332's non-blocking note: agreed that SearchItemSummaries still takes filter, limit and offset as wire inputs. That's deliberate for now. The operation block models the upstream request, and nothing in this change calls it. The frontier row for BrowseSearchPagedCollection states that its dissolution requires the search to be performed under browse_search_page_admission, so the first executing caller has to put the admission and the renderer on the request path. Raising them into the operation's input types is the structural version; it belongs with that caller, not ahead of it.

— sent from sunny-carp-475

gunbc-ci-auto-heal and others added 2 commits September 11, 2026 01:50
Review 63363: SearchItemSummaries defaulted limit to
browse_search_limit_max, so an omitted limit went out as 200 where
upstream's published default is 50. browse_search_limit_default = 50 is
now the operation default. 200 stays the admission ceiling only, and a
caller wanting larger pages says so, which is policy the consumer owns.
The witness admits the default page. All thirteen Browse witnesses pass
locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63363 in 895484e. The finding was right: an omitted limit went out as 200, where the published default is 50. browse_search_limit_default = 50 is now the operation default, following the sibling default_inventory_items_limit pattern, and browse_search_limit_max stays the admission ceiling only. The page-size policy (the buyer schedule's 200-candidate pages) is set by the consumer. The witness now also admits the default page, and all 13 Browse witnesses pass locally.

Review 63380: ebay_browse_frontier_rows had only a shape check beside it,
so the declared debt was invisible to gunbc.dissolution_census. The rows
are now concatenated into gunbc.census_closure_frontier next to
backblaze_b2_frontier_rows, and the witness asserts census membership
for each row, the Cloudflare pattern.

Executed locally: all thirteen Browse witnesses pass, and
annotation_carrier's census_closure_frontier_rows_well_formed holds over
the enlarged census. Removing the enrollment line reds the Browse
membership check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63380 in 0fab94e. ebay_browse_frontier_rows is now concatenated into gunbc.census_closure_frontier next to backblaze_b2_frontier_rows, so gunbc.dissolution_census walks it. witness_browse_frontier_rows_are_well_formed_and_in_the_census asserts census membership for each of the three rows, the Cloudflare pattern.

Checked locally:

  • All 13 Browse witnesses pass.
  • test.claim.annotation_carrier census_closure_frontier_rows_well_formed still holds over the enlarged census.
  • Deleting the enrollment line reds the membership check, so the witness discriminates.

gunbc-ci-auto-heal and others added 2 commits September 11, 2026 06:10
CI's build lane at 0fab94e failed generated-artifact stage0-mirrors on
extdeps_currency_currency.rs: the mirror did not carry the
currency_alphabetic_code this branch added. Regenerated locally with a
claim_executor built from this tree after merging origin/main
(--required-regen). That file was the only drift across the candidate
src/ tree, and the diff is the one function.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

CI at 650621d, attempt 1: none of the failures is in this change.

  • build and heal: toolchain installation died on the runner (rustup: No such file or directory, exit 127/101). That's the shared-runner toolchain eviction, not code.
  • floor: 9 blockers, all v2.test.parse / body_lowering where-refinement witnesses interrupted_before_verdict at the 500 ms ceiling. That's the main-wide cost defect whose repair is in flight. No eBay, currency or quota identity is among them.

The stage0 mirror drift that reddened 0fab94e is fixed in 650621d. I checked it locally with --required-regen, using a claim_executor built from this tree: extdeps_currency_currency.rs was the only drift across the candidate tree.

I cancelled the attempt-2 rerun rather than spend runner slots re-measuring the main defect. I'll merge main and push once the repair lands.

— sent from sunny-carp-475

gunbc-ci-auto-heal and others added 2 commits September 11, 2026 10:27
# Conflicts:
#	dag/gunbc/extdeps_scope_frontier.dag
Review 63740: both Browse operations took marketplace_id as a
NonEmptyStr, beside an annotation documenting that upstream silently
substitutes EBAY_US for an invalid or missing ID. Both now take
extdeps.ebay.ebay MarketplaceEnum, the type the Sell scope already uses,
encoded by marketplace_wire_contract, so neither a missing nor a
misspelled marketplace is writable. All thirteen Browse witnesses pass
locally, and there is no stage0 mirror for this module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63740 in d8b48c1. SearchItemSummaries and GetItem now take marketplace_id: MarketplaceEnum, the extdeps.ebay.ebay type the Sell scope already uses. It goes straight into the header map and marketplace_wire_contract encodes it, the same way the GitHub operations pass enum inputs. So upstream's silent EBAY_US substitution can no longer be reached through a missing or misspelled value. I rewrote the annotation to say this. No marketplace outside the enum is needed, so the enum is unchanged. All 13 Browse witnesses pass locally. They don't call the operations, so for the typed inputs this is a compile check, not an exercised one.

gunbc-ci-auto-heal and others added 2 commits September 11, 2026 10:54
Review 63756: length(ebay_browse_frontier_rows) == 3 was a count copied
from the tree. The witness now joins the rows against the three expected
subjects (browse_production_access, BrowseItem,
BrowseSearchPagedCollection) in both directions, the same shape as the
quota witness's key join. Executed locally: passes clean, and a mutant
that points one row at BrowseItemSummary reds it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7jMc5D8JFmu8US1ZaZk6r
@gunbai-bot

gunbai-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 63756 in a2358c0. The count is gone. witness_browse_frontier_rows_are_well_formed_and_in_the_census now joins ebay_browse_frontier_rows against the three expected subjects by DeclSubject identity, in both directions: every expected subject appears exactly once, and every row is expected. That's the same shape as the quota witness's key join. Checked locally: it passes, and a mutant that points one row at BrowseItemSummary reds it.

# Conflicts:
#	dag/gunbc/extdeps_scope_frontier.dag

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The modeled eBay Browse authority is the right prerequisite for private #58, and I have no content objection at this head. Please refresh this branch onto current public main and rerun witnesses before merging: the only green run is against base f078c59, while current main has advanced by 13 commits. Private #58 must then rebase on private #65's detached-worktree composition fix and consume the landed symbols.

@briansrls
briansrls merged commit 61e60b5 into main Sep 11, 2026
4 checks passed
@briansrls
briansrls deleted the session/sunny-carp-475 branch September 11, 2026 19:05
@briansrls
briansrls restored the session/sunny-carp-475 branch September 11, 2026 19:17
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