Metadata Model¶
NSX uses three metadata layers.
1. Module Metadata: nsx-module.yaml¶
Owned by each module repo.
Declares:
- module identity, type, and version
- backend support flags
- CMake package and target contract
- required and optional module dependencies
- compatibility constraints for board, SoC, and toolchain
2. Curated Lock Metadata: neuralspotx.data/registry.lock.yaml¶
Owned by the NSX tooling repo.
Declares:
- channels such as
stableandpreview - known module entries and project mapping
- starter profiles per board
- default SDK provider revisions for supported boards
3. App Metadata: nsx.yml¶
Owned by each generated app.
Declares:
- project name
- target board and SoC
- toolchain, channel, and profile
- enabled modules and revisions
- optional app-local module registry overrides
Project Record Lifecycle¶
registry.lock.yaml has two independent maps: projects (git/packaged
sources) and modules (module name → project + revision + metadata
path). A module's project field is looked up by name in projects at
resolution time — nothing iterates projects up front, so during automatic
module resolution a projects entry is only ever reached when some
modules.<name>.project, soc_families.<family>.project,
starter_profiles.<profile>.project_overrides, or
starter_profiles.<profile>.module_overrides.<name>.project field names it.
(nsx module register --project <existing-name> can also deliberately reuse
an existing projects record as its source without a module or profile
naming it first — see "Working with Modules" below — so a record can be
structurally unreached today and still be a legitimate, intentionally kept
reuse target; that is what RESERVED_REGISTRY_PROJECT_NAMES documents when
it applies.)
This means consolidating a module's source into another project (e.g. a
one-repo-per-module layout absorbed into a monorepo such as
nsx-ambiq-sdk) is a two-part edit: repoint the modules.<name>.project
field and delete the old projects.<name> record in the same change.
Leaving the old record behind doesn't break anything at runtime (it is
simply never read during automatic resolution), but it rots silently —
pointing at a URL that may no longer exist, be renamed, or be archived —
until someone tries to reuse it as a --project reference or a
module_registry override anchor.
neuralspotx.registry_policy.orphaned_registry_project_report enforces this
contract structurally (deterministic, no network) and runs in
tests/test_stable_registry_policy.py. scripts/audit_registry_project_urls.py
is the companion network audit: it checks that every project's git URL is
actually reachable, with an explicit, documented exemption for
packaged/self-referential projects (neuralspotx) that never need a network
clone in the built-in flow. Run it manually or from a scheduled job — it is
intentionally not part of the normal (network-free) unit-test suite.
If a project record is ever intentionally kept without being referenced —
e.g. as a documented backward-compatible override anchor so
module_registry.modules.<name>.project: <name> keeps working for apps that
pin it without also supplying a module_registry.projects.<name> stanza —
add its name to registry_policy.RESERVED_REGISTRY_PROJECT_NAMES with a
comment explaining the contract. That set is empty today: no first-class
module currently needs it. Once a reservation is no longer needed (the
project record itself was deleted), remove its name from
RESERVED_REGISTRY_PROJECT_NAMES in the same change — a stale reservation
is reported the same way a stale immutable-ref allowance is.
Resolution Order¶
- load the curated lock registry
- merge app-local
module_registryoverrides - resolve the requested module and required dependency closure
- validate compatibility against the app target
- materialize source content from curated module locations
- copy or replace vendored
modules/andboards/content inside the app - update
nsx.ymland generatedcmake/nsx/modules.cmake
Step 2 enforces a two-axis revision precedence: across sources, an
app-authored override layer (a registry.layers entry or the
module_registry block) beats the packaged registry and the synthetic
starter-profile defaults — a layer that pins projects.<p>.revision repins
every module of <p> in the effective registry; within one source,
module-level beats project-level — a layer's own modules.<name>.revision
outranks that same layer's project pin. Module revision selection itself
reads only the merged modules.<name>.revision field
(registry_entry_for_module), so this propagation
(project_config._propagate_layer_project_pins) is what makes app project
pins effective; without it a packaged module-level default would silently
outrank an explicit app pin (issue #218). Between app-authored layers the
later layer still wins, even against an earlier layer's explicit
module-level pin — that stomp is logged as a warning (a specific pin losing
to a general one is legal but never silent; identical warnings are
deduplicated process-wide since the effective registry is recomputed many
times per command), while repinning packaged/profile-sourced module
revisions stays silent because it is the propagation working as intended.
Propagated pins are project-scoped: a later layer that re-points a module
to a different project without expressing a revision restores the value
propagation had replaced, rather than leaking the old project's pin onto
the new project (explicit module-level pins, being module-scoped, survive a
re-point). A project-level revision that is not a string (an unquoted
YAML scalar) raises NSXConfigError instead of being a silently dead pin,
mirroring the loud failure module-level entries already get in
registry_entry_for_module.
The metadata model drives orchestration. CMake remains authoritative for the actual build graph.