Skip to content

KN-86 NoshAPI Versioning Contract

The NoshAPI is the function table that the nOSh runtime exposes to capability modules (cartridges). This document specifies how the nOSh runtime and cartridges negotiate API compatibility using version numbers, an append-only vtable, and rejection semantics.


Each nOSh runtime build declares an api_version (uint16_t, semver-packed: high byte = major, low byte = minor).

  • Major bump: A function signature changed, a function was removed, or semantics changed incompatibly. Old cartridges targeting the previous major version will not work.
  • Minor bump: A new function was appended to the vtable. Old cartridges still work; they simply don’t call the new function.

Current: 0x0100 (v1.0 — initial release)

The oldest cartridge API version this nOSh runtime supports (uint16_t, same format).

  • Cartridges with req_api_version < nosh_runtime.min_api_version are rejected.
  • This allows the nOSh runtime to deprecate and eventually drop old API functions.
  • Guideline: Support at least N-2 minor versions. Only bump min_api_version when a deprecated function is removed from the vtable.

Current: 0x0100 (supports all v1.x cartridges)

Each cartridge header declares the minimum API version it requires (uint16_t, semver-packed).

  • If cartridge.req_api_version > nosh_runtime.api_version, the nOSh runtime cannot satisfy the cartridge’s requirements — reject.
  • If cartridge.req_api_version < nosh_runtime.min_api_version, the cartridge uses deprecated API calls the nOSh runtime no longer supports — reject.

The NoshAPI vtable is an ordered array of function pointers. The order is the contract.

  1. New functions are appended at the end. Never insert in the middle; never reorder existing entries.
  2. Existing function signatures are immutable within a major version. A change to parameters, return type, or documented behavior requires a major version bump.
  3. Existing function semantics are stable. Bug fixes that match documented behavior are fine. Changes that alter observable behavior require a new function (appended) and deprecation of the old one.
  4. Removed functions leave a tombstone. When a function is deprecated and eventually removed, its vtable slot becomes a no-op stub that logs a warning. The slot is never reused.
vtable v1.0 (api_version = 0x0100):
[0] text_clear
[1] text_putc
[2] text_puts
...
[37] draw_bordered_box
vtable v1.1 (api_version = 0x0101):
[0] text_clear ← unchanged
[1] text_putc ← unchanged
...
[37] draw_bordered_box ← unchanged
[38] new_function_a ← appended
[39] new_function_b ← appended

A v1.0 cartridge calls slots [0]–[37] only. When running on v1.1 nOSh runtime, those slots are unchanged — the cartridge works. Slots [38]–[39] exist but the cartridge never calls them.

A v1.1 cartridge may call slots [0]–[39]. If loaded on v1.0 nOSh runtime, the load check sees req_api_version (0x0101) > nosh_runtime.api_version (0x0100) and rejects the cartridge.


The nOSh runtime checks version compatibility immediately after reading the cartridge header:

if (cart.req_api_version > nosh_runtime.api_version) {
/* Cartridge needs a newer nOSh runtime */
reject("MODULE ERROR: requires API v%d.%d (nOSh runtime has v%d.%d)",
MAJOR(cart.req_api_version), MINOR(cart.req_api_version),
MAJOR(nosh_runtime.api_version), MINOR(nosh_runtime.api_version));
}
if (cart.req_api_version < nosh_runtime.min_api_version) {
/* Cartridge uses deprecated API calls */
reject("MODULE ERROR: API v%d.%d no longer supported (min: v%d.%d)",
MAJOR(cart.req_api_version), MINOR(cart.req_api_version),
MAJOR(nosh_runtime.min_api_version), MINOR(nosh_runtime.min_api_version));
}

Clear, operator-facing messages displayed on the main screen:

ConditionMessage
Cart too newMODULE ERROR: requires API vX.Y (nOSh runtime has vA.B)
Cart too oldMODULE ERROR: API vX.Y no longer supported (min: vA.B)
Unknown version formatMODULE ERROR: invalid API version field

After rejection:

  • The cartridge is not loaded (no cart_init called).
  • The emulator returns to the bare deck / mission board.
  • The operator can swap to a compatible cartridge.

The nOSh runtime should support at least N-2 API versions where N is the current minor version.

nOSh runtime APImin_api_versionSupported cartridge range
v1.0v1.0v1.0 only
v1.1v1.0v1.0 – v1.1
v1.2v1.0v1.0 – v1.2
v1.3v1.1v1.1 – v1.3 (v1.0 dropped)
v2.0v2.0v2.0 only (major break)

api_version Semantics: What Constitutes a Bump

Section titled “api_version Semantics: What Constitutes a Bump”
  • New function appended to vtable
  • New optional field added to a struct (with safe zero-default)
  • New event type added to input system
  • New display mode added
  • Function signature changed (parameters, return type)
  • Function removed from vtable (slot becomes tombstone)
  • Struct layout changed in an incompatible way
  • Semantics of an existing function changed
  • Bug fixes that match documented behavior
  • Performance improvements with identical API surface
  • Internal refactoring (implementation detail, not API)
  • New nOSh runtime only features (not exposed to cartridges)

The v1.1 codebase does not yet implement explicit api_version checking. The CartridgeHeader.version field (uint8_t, currently 2) is a header format version, not an API version.

When the v2.0 cartridge format lands (per ADR-0001):

  1. Add api_version (uint16_t) to the nOSh runtime global state
  2. Add req_api_version (uint16_t) to the v2.0 CartridgeHeader
  3. Add min_api_version (uint16_t) to the nOSh runtime global state
  4. Implement the rejection check in cartridge_load_module() / cartridge_load()
  5. Populate the vtable as an explicit indexed array (not scattered function pointers)
  6. Document each vtable slot index in this file as the vtable grows

Reserved for when the explicit vtable is implemented. The 54 primitives from ADR-0005-ffi-surface.md will be assigned stable slot indices here.


The NoshAPI has three access tiers (per ADR-0005-ffi-surface.md):

TierScopeVersion Interaction
Tier 1: All-CartsAlways availableVersioned in the main vtable
Tier 2: Mission-ContextActive mission phases onlyVersioned in the main vtable, guarded by runtime phase state
Tier 3: REPL-Read-OnlyPlayer-facing Lisp REPLSubset of Tier 1; inherits version from main vtable

Tier membership does not affect versioning. All functions occupy a single append-only vtable regardless of tier. Tier access is a runtime guard, not a compile-time split.


  • api_version: nOSh runtime declares; semver-packed uint16_t
  • min_api_version: oldest cartridge API supported by this nOSh runtime
  • req_api_version: cartridge declares; must fall within nOSh runtime’s supported range
  • Vtable: append-only, stable slot indices, tombstones for removed functions
  • Rejection: clear error messages, no partial loading, safe recovery to bare deck
  • Guideline: support N-2 minor versions; major bumps are clean breaks