Migration Guide: Exception Mode → Strict Uniqueness with Multi-Driver

This guide is for clients currently integrated against the exception-mode authorization design (ADR-040 vin_origin_same_company_allowed) who need to migrate to the ADR-042 strict-uniqueness model with multi-driver authorization.

If your integration was built after April 2026 or you've never relied on the exception mode (i.e., you've never set organization_id to bypass strict (VIN, origin) uniqueness), this guide does not apply to you — your existing integration continues to work unchanged under ADR-042's strict-only mode.

Cadence

Your VECU contact will provide the specific migration timeline for your integration. The phases below describe the technical shape of the migration; the dates and sequencing are owned by the chainproofers team and are coordinated with each affected client individually.

What changed

Strict (VIN, geohash) uniqueness, unconditionally

Before (ADR-040 exception mode): the marker key was (VIN, geohash) but the uniqueness predicate could be relaxed per-client when uniqueness_scope = vin_origin_same_company_allowed, permitting multiple concurrent authorizations for the same (VIN, origin) provided each carried a distinct payload organization_id.

After (ADR-042): the marker ConditionExpression is unconditionally attribute_not_exists(PK). There is no exception branch. At most one active authorization exists per (VIN, geohash) regardless of client_id, organization_id, driver_authorization_mode, or any other request shape. A second POST for the same (VIN, geohash) returns 409 DUPLICATE_AUTHORIZATION.

organization_id is forensic-only

Before: organization_id participated in the uniqueness predicate under exception mode and in the 409 disclosure guard (disclose = same_client OR same_company).

After: organization_id is accepted on POST and stored verbatim on the authorization record as caller-asserted forensic metadata only. It does NOT appear in:

  • The marker uniqueness predicate
  • The 409 disclosure guard (now same_client only)
  • Any GSI uniqueness key

It IS still:

  • Echoed back on AuthorizationResponse.organization_id
  • Emitted on custody.authorization.created events for downstream forensic correlation

You can keep sending it; it just doesn't do what it used to.

Disclosure guard simplified to same_client

Before: the 409 envelope's existing_authorization subobject and Location header were disclosed to the caller when either the existing authorization belonged to the same client_id OR both authorizations shared the same non-null organization_id.

After: the disclosure guard is same_client only. Cross-tenant collisions return the 409 status but omit existing_authorization, the flat existingAuthorizationId alias, and the Location header. The same_company clause was removed structurally — it closes a cross-tenant info-leak surface that ADR-040's risk acknowledgment had flagged as a residual.

uniqueness_scope = vin_origin_same_company_allowed is removed

Before: per-client vecu-config field uniqueness_scope accepted two values: vin_origin_strict (default) and vin_origin_same_company_allowed (opt-in for the multi-driver-roster use case).

After: uniqueness_scope is deprecated. The vin_origin_same_company_allowed value is removed entirely. New clients have a different field, driver_authorization_mode, that answers a different question (see below).

What stays the same

Single-driver POST without authorized_drivers works unchanged

If your integration already uses the standard single-driver POST shape with person_identity_key (and never set organization_id to enable exception mode), nothing in your request shape changes. The single-driver call signature is preserved.

The only response-side change you might observe is:

  • uniqueness_scope in the response always returns vin_origin_strict for new authorizations (instead of sometimes returning vin_origin_same_company_allowed). Pre-ADR-042 records may carry historical values for forensic reasons. Do not branch on this field going forward — it will be removed in a future SDK major version.

organization_id request field still accepted

If your integration sends organization_id (formerly to enable exception mode), keep sending it. It is now forensic-only, but the field has not been removed from the API. You can drop it at your convenience; doing so does not change behavior at the custody-service layer.

Marker pattern, atomicity, cross-region semantics

ADR-040's marker-pattern (the atomic attribute_not_exists(PK) invariant), the AC-1..5 acceptance contract, and the cross-region active-active semantics are preserved under ADR-042. Only the exception-mode branch and the same_company disclosure clause were removed.

When to opt into multi-driver mode

Multi-driver mode (ADR-042 driver_authorization_mode = multi_driver) is appropriate when your previous use of exception mode was driven by the multi-driver-roster use case — i.e., creating concurrent authorizations for the same (VIN, origin) so that any driver on a roster could pick up the vehicle.

Under ADR-042, that use case is now expressed as one authorization with N drivers on its authorized_drivers list. Instead of N parallel authorizations sharing a marker, you have a single authorization that carries the full driver list and resolves to one assigned driver via assign_to_driver().

See the per-SDK integration guides for full flows:

If your previous use of exception mode was for some other reason — e.g., multiple distinct authorizations for genuinely independent operations on the same vehicle — talk to your VECU contact. Multi-driver mode does not address that scenario; the structural answer there is typically separate (VIN, origin) tuples (different origin facilities or distinct route legs).

Migration sequence

The migration works in four phases — three customer-facing (Phases 1-3) plus one VECU-internal cleanup (Phase 4) that is invisible to your integration:

Phase 1 — Audit + customer comms

VECU queries existing exception-mode markers (records carrying effective_scope = vin_origin_same_company_allowed) and identifies each affected client. Your VECU contact reaches out to:

  1. Confirm your usage pattern.
  2. Recommend the appropriate post-migration shape (multi-driver mode, or a different structural change if multi-driver doesn't fit).
  3. Coordinate the migration window for your integration.

Phase 2 — Coexistence: ship the new model alongside the old

Both the old uniqueness_scope config and the new driver_authorization_mode config are accepted by VECU during this phase. Your client's existing authorizations continue to work with their stamped-at-creation behavior.

You can:

  • Keep using the existing single-driver POST shape — no code changes required.
  • Begin sandbox testing of the multi-driver shape without waiting for contact-coordination — sandbox clients opted into multi_driver mode are available on request via the standard sandbox provisioning flow. Self-starters can prototype against the new shape ahead of the Phase 3 production cutover.

Phase 3 — Migrate your client config

When you and your VECU contact agree your integration is ready, your client's vecu-config is updated:

  • uniqueness_scope is removed.
  • driver_authorization_mode = multi_driver is set (with the documented risk acknowledgment per ADR-042 AC-5), if applicable.
  • Optionally, driver_list_max is configured if your roster is smaller than the default 25 (the cap is configurable down, not up — the default is also the maximum permitted value).

Your application code continues to work without redeploy. The next authorization you create reflects the new mode.

Phase 4 — Remove the old path (VECU-side)

Once all exception-mode clients have migrated, VECU removes:

  • The marker ConditionExpression's exception-mode branch (already removed structurally as of ADR-042 — Phase 4 is the configuration cleanup).
  • The vin_origin_same_company_allowed config value.
  • SDK deprecation warnings related to the old shape.

This phase is invisible to your integration — it's a VECU-internal cleanup once all clients are off the old path.

Code-level changes for multi-driver migration

If you're moving to multi_driver mode (Phase 3 specifically), you will adopt new SDK methods. The single-driver call shape continues to work unchanged; the new methods are additive.

Old shape (exception mode)New shape (multi-driver mode)
N parallel POSTs with same (VIN, origin) + distinct organization_idOne POST with authorized_drivers list
Cancel non-chosen authorizations to lock in one driverassign_to_driver() (single round-trip; revokes credentials)
Add a driver: POST a new authorizationupdate_drivers(add=[...]) or add_or_create_driver(driver)
Remove a driver: cancel that driver's authorizationupdate_drivers(remove=[pik])
Driver-list source-of-truth: N parallel records keyed by organization_idSingle record's authorized_drivers field

Recommended migration approach:

  1. Read first, write second. Begin consuming the new custody.authorization.modified and custody.authorization.assigned events alongside your existing event consumers — the new events have richer payloads (post-PATCH driver list, revoked credential IDs).
  2. Switch your write path to add_or_create_driver() for the per-driver call shape, or to the explicit create() + update_drivers() + assign_to_driver() triplet for the bulk shape. Both are documented in the per-SDK integration guides.
  3. Remove organization_id from new POSTs at your convenience — you can leave it in for forensic purposes if it's meaningful in your internal logs. It just doesn't drive uniqueness behavior anymore.
  4. Update your audit-trail consumers to read actor_identifier from authorization records and events, if you want a forensic correlation hook for internal users (per ADR-042 §Decision §9).

Compatibility matrix

BehaviorPre-ADR-042 (exception mode)Post-ADR-042 (your client in single_driver)Post-ADR-042 (your client in multi_driver)
Single-driver POST with person_identity_key✅ Works✅ Works (unchanged)✅ Works
Multi-driver POST with authorized_driversn/a❌ 400 MULTI_DRIVER_MODE_REQUIRED✅ Works
organization_id enables relaxed uniqueness✅ (before April 2026)❌ Forensic-only❌ Forensic-only
PATCH /v1/authorizations/{id}/driversn/a❌ 400 MULTI_DRIVER_MODE_REQUIRED✅ Works
PUT /v1/authorizations/{id}/assignn/a✅ Works (no-op-shaped on 1-driver list)✅ Works
custody.authorization.modified / .assigned eventsn/aEmitted only on .assigned (no-op shape)Emitted on every PATCH and PUT

Questions

If you're uncertain whether a particular usage pattern is affected, or which migration path applies to your integration, contact your VECU chainproofers contact. The technical details above describe the model; your VECU contact will confirm the specifics for your client.

References