NVS Record IDs as a Product Interface: Prevent Reusing an Old ID for New Meaning
Treat each persistent NVS record ID as an assigned product interface. Once shipped, its meaning must remain stable or be retired explicitly. Reusing an old ID for a different setting can make intact, readable bytes become the wrong instruction to new firmware.
This article targets direct use of Zephyr v4.2.0 NVS and the application’s record-ID registry. If a settings backend or another library owns the NVS region, its namespace rules take precedence; do not allocate private IDs inside its store without reviewing that ownership. The goal is preventing semantic ID collisions, rather than explaining a general schema-migration architecture.
1. Recognize what NVS identifies
The v4.2.0 API accepts a uint16_t ID when writing or reading a record. The storage layer associates that ID with bytes. It does not know that the bytes represent a temperature offset, a motor limit, or a timeout in milliseconds.
Stored metadata includes the ID and data length, as described in the NVS storage format overview. Those fields cannot establish application meaning. A length check can reject a four-byte value where two bytes were expected; it cannot distinguish two unrelated meanings that happen to share a representation.
Integrity protection has the same boundary. A checksum can help detect changed bytes but cannot tell a new decoder that an old ID once had another unit or purpose. Semantic ownership belongs in the product’s compatibility design and tests.

2. Create an append-only assignment registry
Keep one authoritative registry for every component that writes to the same NVS instance. Record the ID, symbolic name, owner, first shipped release, payload representation, units, valid range, decoder versions, default policy, and retirement status. Review assignments as part of interface changes, not only at the end of a firmware release.
| Registry field | Question it must answer |
|---|---|
| Numeric ID and symbolic name | Which bytes on existing devices belong to this setting? |
| Owning component | Who may change the value and approve compatibility changes? |
| Meaning and units | What physical or product quantity is represented? |
| Encoding | What are the byte order, signedness, length, and payload version rules? |
| Reader range | Which released readers can interpret each allowed encoding? |
| Missing and invalid policies | When is a default allowed, and when must the product stop or report a fault? |
| Lifecycle | Is the ID active, deprecated, or permanently retired from allocation? |
Allocate within the real application namespace. The v4.2.0 implementation uses 0xFFFF for sector-control entries, so do not treat every possible 16-bit value as a freely available application ID. Other reserved ranges may come from your wrapper or product, and must be checked separately.
3. Distinguish deletion from retirement
Deleting a value on one device does not make its numeric ID available for a new meaning. Other devices can still contain the old record, an update can be interrupted before deletion, and older firmware can write it again. The registry must retain the assignment even when the current release no longer uses it.
NVS deletion is represented by a zero-length write in the API contract, with nvs_delete() calling the write path in the pinned implementation. That operation changes the stored value’s availability. It does not erase knowledge of the ID’s historical meaning from the product population.
Use a retired-ID entry in source control even after removing its active decoder. If storage cleanup is authorized, keep it separate from ID allocation. Never use “we deleted this during update” as the sole evidence that reuse is safe across every shipped device and rollback path.
4. Decide whether a change keeps the same meaning
A compatible representation extension may remain under the same ID if every permitted reader has a defined handling rule. A change in physical units, signedness, range interpretation, or purpose needs explicit compatibility review. When the existing meaning cannot remain stable, assign a new ID and retain the old assignment as deprecated or retired.
Do not rely on an unversioned C structure layout as the registry’s encoding specification. Compiler padding and a reordered field can change bytes without changing the record ID. Define the serialized representation and validate it before applying values to hardware.
Keep the review focused: the registry does not by itself prove that a larger migration is atomic or rollback-safe. It supplies the semantic ownership that such a design needs. Any conversion or coexistence mechanism requires its own release qualification.
5. Test old bytes against every permitted reader
Preserve fixtures from real released encoders or generate them from their versioned serialization definitions. A fixture should identify the originating release, ID, payload bytes, and intended meaning. Do not create old-format fixtures using only the newest serializer and assume they represent previous products.
- Load each old fixture into the new reader and verify either the correct meaning or an explicit incompatibility result.
- Test equal-length but semantically wrong payloads, including changed units and signedness.
- Boot permitted older firmware against a copy containing new records and confirm it ignores or rejects unknown IDs according to policy.
- Test a retired ID that remains present after an interrupted cleanup.
- Check that two components cannot claim the same ID in one build configuration.
- Check the registry’s active and retired sets against newly proposed assignments during review or CI.
Perform write-based fixtures on disposable test regions or preserved copies, with valuable unit data excluded. These are proposed compatibility tests; no target result is claimed here.
Hypothetical collision review
A hypothetical old release uses ID 0x0120 for a fan-speed limit in revolutions per minute. A new component proposes the same ID for a timeout in milliseconds because the original feature was removed. Both values are unsigned two-byte quantities, and 1,200 is plausible in either role. Neither record length nor intact storage resolves the ambiguity.
The registry rejects the assignment. The old ID stays retired under its original meaning, and the timeout receives a new approved ID. The new reader is tested with the old record still present. This prevents the collision without pretending that all existing devices have already been cleaned.
Release the registry and old-byte fixtures with the firmware. A compact numeric key is still a long-lived interface when it survives software updates.