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_clientonly) - Any GSI uniqueness key
It IS still:
- Echoed back on
AuthorizationResponse.organization_id - Emitted on
custody.authorization.createdevents 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_scopein the response always returnsvin_origin_strictfor new authorizations (instead of sometimes returningvin_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:
- Confirm your usage pattern.
- Recommend the appropriate post-migration shape (multi-driver mode, or a different structural change if multi-driver doesn't fit).
- 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_drivermode 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_scopeis removed.driver_authorization_mode = multi_driveris set (with the documented risk acknowledgment per ADR-042 AC-5), if applicable.- Optionally,
driver_list_maxis 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_allowedconfig 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_id | One POST with authorized_drivers list |
| Cancel non-chosen authorizations to lock in one driver | assign_to_driver() (single round-trip; revokes credentials) |
| Add a driver: POST a new authorization | update_drivers(add=[...]) or add_or_create_driver(driver) |
| Remove a driver: cancel that driver's authorization | update_drivers(remove=[pik]) |
Driver-list source-of-truth: N parallel records keyed by organization_id | Single record's authorized_drivers field |
Recommended migration approach:
- Read first, write second. Begin consuming the new
custody.authorization.modifiedandcustody.authorization.assignedevents alongside your existing event consumers — the new events have richer payloads (post-PATCH driver list, revoked credential IDs). - Switch your write path to
add_or_create_driver()for the per-driver call shape, or to the explicitcreate()+update_drivers()+assign_to_driver()triplet for the bulk shape. Both are documented in the per-SDK integration guides. - Remove
organization_idfrom 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. - Update your audit-trail consumers to read
actor_identifierfrom authorization records and events, if you want a forensic correlation hook for internal users (per ADR-042 §Decision §9).
Compatibility matrix
| Behavior | Pre-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_drivers | n/a | ❌ 400 MULTI_DRIVER_MODE_REQUIRED | ✅ Works |
organization_id enables relaxed uniqueness | ✅ (before April 2026) | ❌ Forensic-only | ❌ Forensic-only |
PATCH /v1/authorizations/{id}/drivers | n/a | ❌ 400 MULTI_DRIVER_MODE_REQUIRED | ✅ Works |
PUT /v1/authorizations/{id}/assign | n/a | ✅ Works (no-op-shaped on 1-driver list) | ✅ Works |
custody.authorization.modified / .assigned events | n/a | Emitted 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
- ADR-042: Strict Uniqueness with Multi-Driver Authorization — canonical decision record
- ADR-040: Payload-Sourced
organization_id— partially superseded by ADR-042 - Multi-Driver Integration Guide (Python)
- Multi-Driver Integration Guide (.NET)
- Custody Permits API Reference (Python)
- Custody Permits API Reference (.NET)
- Custody Events