Skip to main content

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 by check_contract_gate.py. Keep this table in sync with the sidecar — every discovered endpoint/entity/FK in requirements must have a matching category:item gate, and every HARD row should read ✅ PASS or 🟡 OVERRIDE: <reason> (user-approved) before check_contract_gate.py passes 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 by public-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:dealer blocker is resolved by replacing the zip sweep with inventory-BAC enumeration plus quantum-dealer-locator resolution (2026-07-23; see Dealer Source).
  • Platform: Tekion AEC-CP, reverse-proxied through each brand marketing domain www.<brand>.com/<brand>/shopping/api/... (carries client: T1_VSR) for inventory and configurator. Dealers now use GM's brand-domain quantum-dealer-locator endpoint. Shared across all four US brands (Chevrolet, Buick, GMC, Cadillac) — identical structures, only programId/make + subdomain change for AEC-CP calls.

Gate summary

#GateEntity / paramStatusEvidence
1Pagination contractinventory✅ PASSpagination_probe — 147/147, cursor
2Enumerator (model)models✅ PASSenumerator_probe — 24 via vehicles/facets
3Enumerator (year)models✅ PASSenumerator_probe — 5 via /bootstrap
4Enumerator (trim)features✅ PASSenumerator_probe — trimCodes via /trim
5Enumerator (dealer)dealers✅ PASSinventory raw enumerates BACs; quantum-dealer-locator resolves each BAC
6Multi-value filterinventory⬜ N/Aone model value per cursor chain; never combined
7Body shape / bot-blockinventory✅ PASSbody_shape_probe — 4/4 JSON
8Body shape / bot-blockconfigurator✅ PASSbody_shape_probe — /bootstrap + /trim JSON
9Data quality / plausibilityinventory✅ PASSdata_quality_probe
10Data quality / plausibilitydealers✅ PASSdata_quality_probe — real non-zero coords
11Data quality / plausibilitymodels✅ PASSdata_quality_probe
12Data quality / plausibilityfeatures✅ PASSdata_quality_probe
13Cross-source identity keyinventory→dealers✅ PASSdealer.bac ↔ quantum bac, scoped by brand
14Cross-source identity keyinventory→models✅ PASS2/2 variant.code ⊂ trimCode
15Cross-source identity keyinventory→features✅ PASSvariant.code ↔ model_code (both trimCode)
16Cross-source identity keyfeatures→models✅ PASSmodel_code == trimCode by construction
17Real fixture (no synthetic)inventory✅ PASStests/data/us/inventory_search_sample.json
18Real fixture (no synthetic)dealers✅ PASStests/data/us/quantum_dealers_sample.json
19Real fixture (no synthetic)models✅ PASStests/data/us/configurator_trim_sample.json
20Real fixture (no synthetic)features✅ PASStests/data/us/standard_features_sample.json
21Postman recordinventory✅ PASSrecorded + validated in Public OEM APIs GM folder; offset→cursor fixed 2026-07-22
22Postman recorddealers✅ PASSrecorded + validated (aecloud.io locate-dealers; quantum-dealer-locator also added)
23Postman recordmodels✅ PASSrecorded + validated (/trim + CATALOGUE/BOOTSTRAP helpers)
24Postman recordfeatures✅ PASSrecorded + validated (/standard-features + /configurator-state; /load retired after coverage validation)

Per-entity endpoints

EntityEndpoint (method + URL)Auth / protocolFixtureNotes
inventoryPOST www.<brand>.com/<brand>/shopping/api/aec-cp-discovery-api/p/v1/vehicles/searchAEC-CP headers + client:T1_VSR; Akamai (AkamaiSession impersonation)inventory_search_sample.jsonrecords data.hits; id=id (VIN); total data.count; cursor data.pagination.nextPageToken; size hard-caps at 20; offset ignored
dealersGET www.<brand>.com/bypass/pcf/quantum-dealer-locator/v1/dealerId/<BAC>clientapplicationid: quantum; AkamaiSession in production for consistencyquantum_dealers_sample.jsonrecords payload.dealers[]; BACs enumerated from inventory raw; extractor emits one row per brand franchise (bac + dealer_code + brand)
modelsGET /bootstrapPOST /cataloguePOST /trim (aec-cp-configurator-gateway/p/v1)AEC-CP headers + client:T1_VSR; Akamaiconfigurator_trim_sample.jsontrims data.trims keyed by styleId; id=trimCode; MSRP trims.<sid>.msrp.value; combos from /catalogue drive /trim fan-out
featuresPOST /standard-features (standard equip) + POST /configurator-state (priced, carries model_code)AEC-CP headers + client:T1_VSR; Akamaistandard_features_sample.jsonrecords 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/facetsdata.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.

FKReferencing entity.fieldTarget entity.fieldExample valueVerified
inventory→dealersinventory dealer.bac / transformed dealer_codedealers bac + brand111217, 286536✅ BAC space verified; implemented FK is dealer_code>bac plus brand
inventory→modelsinventory variant.codemodels trimCode1TR58_1LS, 1TR58_1RS✅ 2/2 subset of trax trimCodes
inventory→featuresinventory variant.codefeatures model_code1TU58_1LT✅ same trimCode space
features→modelsfeatures model_code (=req trimCode)models trimCode1TR58_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 at data.pagination.nextPageToken), size pagination.size=20
  • Termination: null cursor
  • Completeness: fetched 147 unique records vs reported total 147 across 8 page(s).
  • Filter used: model=corvette e-ray from central zip 66952, radius 5000 (national total 147, fits within max-pages). size hard-caps at 20 regardless of requested value; an offset field is silently ignored.

2. Enumerator — model

Enumerator — ✅ PASS

  • POST .../aec-cp-discovery-api/p/v1/vehicles/facets enumerates 24 distinct values model 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/bootstrap enumerates 5 distinct value years [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 distinct trimCode values, 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 /trim per catalog combo; never hardcode a lineup.

5. Enumerator — dealer — ✅ PASS (remediated 2026-07-23)

  • locate-dealers requires a zipCode and 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 whose makeCodes match 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>.com seed for ak_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 /trim POST with an invalid (model, bodyStyle) combo returns request errors — combos MUST come from /catalogue, not guessed. A 200 can also carry status:"failed" with errorDetails.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.bac all nonempty (0% degenerate); pricing.cash.msrp.value positive (26385/28085). Note: dealer.bac zero-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: bac nonempty; geolocation.latitude/longitude are real non-zero coords (41.09782 / -73.99745), 0% degenerate. The sample has four franchises at one BAC; the implemented extractor emits the brand-matching bac + dealer_code + brand row.

11. Data quality — models

Data quality (models) — ✅ PASS

  • data.trims: trimCode nonempty; msrp.value positive (23495). NOTE: live replay found msrp.value can 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.jsonPOST vehicles/search (model=trax) → data.hits
  • packages/ai_public_api/tests/data/gm/us/quantum_dealers_sample.jsonGET quantum-dealer-locatorpayload.dealers
  • packages/ai_public_api/tests/data/gm/us/configurator_trim_sample.jsonPOST /trim (trax) → data.trims
  • packages/ai_public_api/tests/data/gm/us/standard_features_sample.jsonPOST /standard-features (trax) → data.trim.highlights[]
  • packages/ai_public_api/tests/data/gm/us/vehicles_facets_sample.jsonPOST 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

GateOVERRIDE reasonApproved by
enumerator:dealerNational coverage = external US zip sweep deduped on vendor.id FALSIFIED 2026-07-22: sweep covers 62.4% CONUS / ~60% of dealers (~40% gap). Replaced by inventory-BAC enumeration + quantum BAC resolution.resolved 2026-07-23