Discovery Contract — gm (us)
The vetting gate between discovery and coding. This file is the human-written description + probe evidence; the authoritative gate state lives in the sidecar
discovery-contract.gate.json, validated bycheck_contract_gate.py. Keep this table in sync with the sidecar — every discovered endpoint/entity/FK inrequirementsmust have a matchingcategory:itemgate, and every HARD row should read✅ PASSor🟡 OVERRIDE: <reason>(user-approved) beforecheck_contract_gate.pypasses and coding may begin. A Postman row may instead read📦 MANUAL-IMPORT: <path>when the Postman MCP was unavailable and the collection was exported for hand-import — that state passes the gate but stays flagged. Probe markdown blocks are pasted in verbatim bypublic-api-contract-test.
- OEM / market:
gm/us - Discovered:
2026-07-21(gm-rediscovery pass) - Gate status: ✅ pass — every HARD probe gate passes. The former
enumerator:dealerblocker is resolved by replacing the zip sweep with inventory-BAC enumeration plusquantum-dealer-locatorresolution (2026-07-23; see Dealer Source). - Platform: Tekion AEC-CP, reverse-proxied through each brand marketing domain
www.<brand>.com/<brand>/shopping/api/...(carriesclient: T1_VSR) for inventory and configurator. Dealers now use GM's brand-domainquantum-dealer-locatorendpoint. Shared across all four US brands (Chevrolet, Buick, GMC, Cadillac) — identical structures, onlyprogramId/make+ subdomain change for AEC-CP calls.
Gate summary
| # | Gate | Entity / param | Status | Evidence |
|---|---|---|---|---|
| 1 | Pagination contract | inventory | ✅ PASS | pagination_probe — 147/147, cursor |
| 2 | Enumerator (model) | models | ✅ PASS | enumerator_probe — 24 via vehicles/facets |
| 3 | Enumerator (year) | models | ✅ PASS | enumerator_probe — 5 via /bootstrap |
| 4 | Enumerator (trim) | features | ✅ PASS | enumerator_probe — trimCodes via /trim |
| 5 | Enumerator (dealer) | dealers | ✅ PASS | inventory raw enumerates BACs; quantum-dealer-locator resolves each BAC |
| 6 | Multi-value filter | inventory | ⬜ N/A | one model value per cursor chain; never combined |
| 7 | Body shape / bot-block | inventory | ✅ PASS | body_shape_probe — 4/4 JSON |
| 8 | Body shape / bot-block | configurator | ✅ PASS | body_shape_probe — /bootstrap + /trim JSON |
| 9 | Data quality / plausibility | inventory | ✅ PASS | data_quality_probe |
| 10 | Data quality / plausibility | dealers | ✅ PASS | data_quality_probe — real non-zero coords |
| 11 | Data quality / plausibility | models | ✅ PASS | data_quality_probe |
| 12 | Data quality / plausibility | features | ✅ PASS | data_quality_probe |
| 13 | Cross-source identity key | inventory→dealers | ✅ PASS | dealer.bac ↔ quantum bac, scoped by brand |
| 14 | Cross-source identity key | inventory→models | ✅ PASS | 2/2 variant.code ⊂ trimCode |
| 15 | Cross-source identity key | inventory→features | ✅ PASS | variant.code ↔ model_code (both trimCode) |
| 16 | Cross-source identity key | features→models | ✅ PASS | model_code == trimCode by construction |
| 17 | Real fixture (no synthetic) | inventory | ✅ PASS | tests/data/us/inventory_search_sample.json |
| 18 | Real fixture (no synthetic) | dealers | ✅ PASS | tests/data/us/quantum_dealers_sample.json |
| 19 | Real fixture (no synthetic) | models | ✅ PASS | tests/data/us/configurator_trim_sample.json |
| 20 | Real fixture (no synthetic) | features | ✅ PASS | tests/data/us/standard_features_sample.json |
| 21 | Postman record | inventory | ✅ PASS | recorded + validated in Public OEM APIs GM folder; offset→cursor fixed 2026-07-22 |
| 22 | Postman record | dealers | ✅ PASS | recorded + validated (aecloud.io locate-dealers; quantum-dealer-locator also added) |
| 23 | Postman record | models | ✅ PASS | recorded + validated (/trim + CATALOGUE/BOOTSTRAP helpers) |
| 24 | Postman record | features | ✅ PASS | recorded + validated (/standard-features + /configurator-state; /load retired after coverage validation) |
Per-entity endpoints
| Entity | Endpoint (method + URL) | Auth / protocol | Fixture | Notes |
|---|---|---|---|---|
| inventory | POST www.<brand>.com/<brand>/shopping/api/aec-cp-discovery-api/p/v1/vehicles/search | AEC-CP headers + client:T1_VSR; Akamai (AkamaiSession impersonation) | inventory_search_sample.json | records data.hits; id=id (VIN); total data.count; cursor data.pagination.nextPageToken; size hard-caps at 20; offset ignored |
| dealers | GET www.<brand>.com/bypass/pcf/quantum-dealer-locator/v1/dealerId/<BAC> | clientapplicationid: quantum; AkamaiSession in production for consistency | quantum_dealers_sample.json | records payload.dealers[]; BACs enumerated from inventory raw; extractor emits one row per brand franchise (bac + dealer_code + brand) |
| models | GET /bootstrap → POST /catalogue → POST /trim (aec-cp-configurator-gateway/p/v1) | AEC-CP headers + client:T1_VSR; Akamai | configurator_trim_sample.json | trims data.trims keyed by styleId; id=trimCode; MSRP trims.<sid>.msrp.value; combos from /catalogue drive /trim fan-out |
| features | POST /standard-features (standard equip) + POST /configurator-state (priced, carries model_code) | AEC-CP headers + client:T1_VSR; Akamai | standard_features_sample.json | records data.trim.highlights[] and data.workflow[]; feature_code=RPO code in both; model_code=request trimCode; /configurator-state fetches every nonempty configurations[].ss value and only emits a trim failure when all states fail; data.categories[].items[] not extracted (no per-item code — see the data-quality note below); /load retired after coverage validation |
Fan-out enumerator: POST vehicles/facets → data.model[].values (24 lowercase model codes)
drives the per-model inventory cursor chain.
Cross-source identity / FK alignment (HARD)
Value-matched (not name-matched) join keys, verified live 2026-07-21.
| FK | Referencing entity.field | Target entity.field | Example value | Verified |
|---|---|---|---|---|
| inventory→dealers | inventory dealer.bac / transformed dealer_code | dealers bac + brand | 111217, 286536 | ✅ BAC space verified; implemented FK is dealer_code>bac plus brand |
| inventory→models | inventory variant.code | models trimCode | 1TR58_1LS, 1TR58_1RS | ✅ 2/2 subset of trax trimCodes |
| inventory→features | inventory variant.code | features model_code | 1TU58_1LT | ✅ same trimCode space |
| features→models | features model_code (=req trimCode) | models trimCode | 1TR58_1LS | ✅ recorded from /trim at fetch time |
Join keys: inventory→dealers on transformed dealer_code (the inventory
dealer.bac) to dealers bac, plus brand. inventory→models /
inventory→features join on [variant.code == trimCode/model_code, year == model_year]. features→models joins on [model_code == trimCode]. Do not use the
5-digit quantum dealerCode as the inventory FK target.
Probe evidence
1. Pagination contract — inventory
Pagination contract — ✅ PASS
- Endpoint:
POST https://www.chevrolet.com/chevrolet/shopping/api/aec-cp-discovery-api/p/v1/vehicles/search - Contract: cursor via
pagination.nextPageToken(next atdata.pagination.nextPageToken), sizepagination.size=20 - Termination: null cursor
- Completeness: fetched 147 unique records vs reported total 147 across 8 page(s).
- Filter used: model=
corvette e-rayfrom central zip 66952, radius 5000 (national total 147, fits within max-pages).sizehard-caps at 20 regardless of requested value; anoffsetfield is silently ignored.
2. Enumerator — model
Enumerator — ✅ PASS
POST .../aec-cp-discovery-api/p/v1/vehicles/facetsenumerates 24 distinctvaluesmodel codes, e.g.['blazer'],['bolt'],['corvette e-ray']. Iterate these live for the per-model inventory cursor fan-out; do not hardcode.
3. Enumerator — year
Enumerator — ✅ PASS
GET .../aec-cp-configurator-gateway/p/v1/bootstrapenumerates 5 distinctvalueyears[2024, 2025, 2026, 2027, 2028].bodySegments(quickFilter) enumerated live too:[ELECTRIC, SUV, TRUCK, PERFORMANCE, VAN]. These feed/catalogue, which yields the(year, model, bodyStyle)combos. Iterate live.
4. Enumerator — trim
Enumerator — ✅ PASS
POST .../aec-cp-configurator-gateway/p/v1/trim(combo 2027/bolt/bolt) enumerates distincttrimCodevalues, e.g.1FF48_2LT,1FG48_2RS. Trax across MY2025/2026:1TR58_1LS, 1TR58_1RS, 1TU58_1LT, 1TU58_1SA, 1TU58_2RS. Trim fan-out codes come from live/trimper catalog combo; never hardcode a lineup.
5. Enumerator — dealer — ✅ PASS (remediated 2026-07-23)
locate-dealersrequires azipCodeand hard-caps at 50 vendors/call; the onboarding contract accepted an override asserting national coverage via an external US zip sweep. Hands-on verification (2026-07-22) disproved that assertion. The 58-zip / 100mi sweep covers only 62.4% of CONUS and captures ~60% of dealers-with-inventory (2,296 of 3,836) — a ~40% gap (~89% geographic holes, ~11% the 50-cap). It also found 3 hallucinated sweep zips with zero coverage. A direct BAC-resolution endpoint (quantum-dealer-locator) was found that eliminates the need for a sweep.- Remediation: the AEC-CP zip-sweep source was deleted and replaced with
quantum_dealers. Each brand's quantum raw asset reads distinct BACs from its corresponding inventory raw asset, resolves each BAC by direct endpoint, and the transformed extractor keeps the franchise whosemakeCodesmatch the brand. Full analysis and decision record: Dealer Source.
6. Multi-value filter — inventory
N/A — inventory fans out one model value per cursor chain (filters.model.values holds a
single model from the facets enumerator). No multi-value filter is ever combined into one
call, so the contamination/relabel probe does not apply.
7. Body shape / bot-block — inventory
Body shape / bot-block — ✅ PASS
- Endpoint:
POST .../vehicles/search - Every 2xx response with a realistic UA parsed as JSON. Default-UA block rate 0%; realistic-UA block rate 0%.
- NOTE: production uses
AkamaiSession(curl_cffi chrome TLS impersonation +www.<brand>.comseed forak_bmsc/bm_sv) for robustness at volume. The discovery-summary claim that plain httpx is always blocked did not reproduce from this network this pass, but impersonation remains the safe default.
8. Body shape / bot-block — configurator
Body shape / bot-block — ✅ PASS
GET /bootstrap: 4/4 JSON, 0% block.POST /trim(valid catalogue combo 2027/bolt/bolt): 4/4 JSON, 0% block.- A
/trimPOST with an invalid(model, bodyStyle)combo returns request errors — combos MUST come from/catalogue, not guessed. A 200 can also carrystatus:"failed"witherrorDetails.key(unsupported.vehicle/unknown.handled) for models with no configurator support — suppress, do not abort.
9. Data quality — inventory
Data quality (inventory) — ✅ PASS
data.hits:id(VIN),variant.code,dealer.bacall nonempty (0% degenerate);pricing.cash.msrp.valuepositive (26385/28085). Note:dealer.baczero-variance in the 2-row fixture (single-dealer capture) — assessed on volume in raw/transformed EDA.
10. Data quality — dealers
Data quality (dealers) — ✅ PASS
payload.dealers:bacnonempty;geolocation.latitude/longitudeare real non-zero coords (41.09782 / -73.99745), 0% degenerate. The sample has four franchises at one BAC; the implemented extractor emits the brand-matchingbac + dealer_code + brandrow.
11. Data quality — models
Data quality (models) — ✅ PASS
data.trims:trimCodenonempty;msrp.valuepositive (23495). NOTE: live replay foundmsrp.valuecan be null on some EV trims (equinox-ev MY2026) — a partial-coverage note for full-volume EDA, not a degenerate-source FAIL.
12. Data quality — features
Data quality (features) — ✅ PASS
data.trim.highlights[]:code(RPO) 100% nonempty and unique within a trim; 6 records per trim.
13–16. Cross-source identity keys
See the FK alignment table above — all four value-matched live 2026-07-21. The dealer
implementation now avoids the old vendor.id number/string mismatch by storing quantum
bac as a string and joining transformed inventory dealer_code to dealers bac, scoped
by brand.
17–20. Real fixtures
packages/ai_public_api/tests/data/gm/us/inventory_search_sample.json—POST vehicles/search(model=trax) →data.hitspackages/ai_public_api/tests/data/gm/us/quantum_dealers_sample.json—GET quantum-dealer-locator→payload.dealerspackages/ai_public_api/tests/data/gm/us/configurator_trim_sample.json—POST /trim(trax) →data.trimspackages/ai_public_api/tests/data/gm/us/standard_features_sample.json—POST /standard-features(trax) →data.trim.highlights[]packages/ai_public_api/tests/data/gm/us/vehicles_facets_sample.json—POST vehicles/facets(model enumerator) →data.model[].values
All registered as @pytest.fixtures in packages/ai_public_api/tests/gm/conftest.py.
21–24. Postman records
✅ PASS — recorded and validated in the "Public OEM APIs" collection GM folder (all 4 brands;
inventory / dealers / models / features + FACETS/CATALOGUE/BOOTSTRAP helpers) on 2026-07-22.
The quantum-dealer-locator BAC endpoint was added alongside (see Dealer Source).
Overrides
| Gate | OVERRIDE reason | Approved by |
|---|---|---|
| enumerator:dealer | vendor.id | resolved 2026-07-23 |