Skip to content

Latest commit

 

History

51 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Marginalia SDK

Developer SDK for Marginalia packages.

This repo defines the shared contract between package authors, the firmware, and the registry/hub:

  • manifest schema
  • v1 compatibility kinds and execution classes
  • v2 component roles, activation, and semantic requirements
  • freestanding C ABI v1 header for native components
  • compatibility rules
  • reference package structure

Current scope

The v1 contract remains supported for the Xteink X3/X4 firmware line. Manifest v2 is the migration contract for multi-component packages; current firmware can consume its declarative shape and admits the first native Service host for the marginalia-c-1 ABI. App and Provider roles remain gated on their role-specific hosts. Native package activation remains gated on measured hardware admission and recovery validation, so a structurally valid package may still be installable while its executable components are unavailable.

Installation and activation are separate lifecycle events. Firmware may report activationPending after it has validated and staged an archive; package files are not hot-swapped into running hosts, and the candidate is not a confirmed running version until a later boot completes its recovery trial. This is device-local state, not a new manifest field or a registry status.

Package model

Marginalia packages are either:

  • static: validated manifest data with no executable package runtime
  • module: loaded into the firmware host for themes, sleep screens, reader hooks, widgets, and integrations
  • app: a standalone experience with its own navigation lifecycle

Package kinds:

  • theme
  • sleep_screen
  • reader_module
  • integration
  • app

Packages can also declare static contributes entries for firmware-visible extension points. The first contribution point is catalog.providers, which lets integration packages add OPDS/RWPM-style catalog providers without adding a new firmware feature for each provider. Static contribution packages should use execution: "static" and do not need runtime entrypoints.

Manifest v2 replaces package-wide behavior with components of type app, service, provider, or contribution. Use --schema-version 2 with the scaffolder to create a v2 package. The SDK normalizes v1 and v2 to the same component view for validation. Native executable components currently use the fixed ABI-v1 export marginalia_module_entry_v1; role callbacks are selected by the descriptor returned from that export. App components use the ABI-v1 complete packed 1-bpp framebuffer target and semantic input event definitions in native_abi_v1.h. The firmware owns the foreground loop and display timing; the SDK header does not grant direct renderer, GPIO, raw SD, or RTOS access. ABI minor 2 adds a bounded asynchronous package asset/data storage contract; read completions are delivered through typed events and larger reads are chunked by the package. The same descriptor carries bounded resource declarations; the device profile, not the catalog, decides whether an installed native component can activate.

Start here

Validate a manifest

python3 tools/validate_manifest.py path/to/manifest.json

The default local profile matches what current firmware accepts for SD/Wi-Fi side loading. Use the stricter publish profile for packages that are ready for a registry or hub:

python3 tools/validate_manifest.py --profile publish path/to/manifest.json

The validator selects the schema from schemaVersion. Compatibility failures are reported separately from schema errors; a structurally valid v2 native package may be installable while one executable role remains unavailable until its role-specific native ABI host exists.

Create a package

python3 tools/create_package.py org.example.hangman "Hangman" --kind app --output ./packages

For the v2 component shape:

python3 tools/create_package.py org.example.dark-mode "Dark Mode" --schema-version 2 --kind theme --output ./packages

The command writes a side-loadable package folder with:

  • manifest.json
  • README.md
  • src/entrypoints.json

Use --profile local for the smallest manifest accepted by current firmware. Use the default publish profile when the package is meant to become a registry or hub entry.

Build an archive

python3 tools/build_package.py ./packages/hangman --output ./dist --json

The builder validates the manifest, binds a v2 native component's inner SHA-256 to the exact file it archives, runs the dependency-free native ELF preflight, rejects paths the firmware side-loader would reject, creates a deterministic package archive name, and prints the archive SHA-256 checksum that registry and hub entries should publish. Firmware re-runs the authoritative verifier after download.

Legacy schema v1 packages remain metadata/compatibility packages and do not need a native ELF artifact; the native artifact binding contract starts with schema v2.

To inspect an artifact without building an archive:

python3 tools/inspect_native_module.py path/to/module.native --json

Firmware-safe archives are capped at 96 files, 512 KiB total uncompressed data, 128 KiB per file, and 180 bytes per relative archive path.

For manifest v2, components[].requires describes semantic host capabilities. The firmware evaluates those requirements against its active hardware profile and role host at scan time, so a missing component capability can leave the package installed and visible while that component remains unavailable. SDK/registry compatibility is intentionally package-level and does not make a missing component capability a safety verdict or package-deletion decision.

The optional root dependencies list describes package activation prerequisites. Each entry is { "id", "version" }, where version is a minimum MAJOR.MINOR.PATCH version. It does not download or enable anything: the device user must install and enable every dependency. A missing, disabled, incompatible, quarantined, cyclic, or too-old dependency leaves the declaring package installed but unavailable until the user changes the package set. Dependencies are resolved from one bounded device snapshot and are started before dependents; teardown is attempted in reverse order.

The X3/X4 App role currently has a hardware-only fixture in ../marginalia-examples/hardware-fixtures/native-smoke-app. Its local archive and ELF preflight are useful for the loader matrix, but the normal target profile still reports App ABI support as unavailable until physical X3/X4 results exist. The typed Provider role has the corresponding deterministic fixture in ../marginalia-examples/hardware-fixtures/native-reader-sync-provider; it exercises reader.sync/1 records without opening a transport or reading credentials. Its preflight is likewise not a runtime or safety verdict, and the normal target profile keeps Provider ABI support unavailable until the network/credential host and physical recovery matrix are complete.

Build a registry entry

python3 tools/build_catalog_entry.py ./packages/hangman \
  --archive-output ./dist \
  --entry-output ./entries/org.example.hangman.json \
  --artifact-url https://example.org/org.example.hangman-0.1.0.mpkg.zip \
  --source-url https://github.com/example/marginalia-packages.git \
  --source-ref v0.1.0 \
  --source-path hangman

The command builds the .mpkg.zip, computes its checksum and size, and writes a catalog entry shaped for marginalia-registry. The source fields make registry entries reviewable in the same spirit as RT-Thread package metadata: the artifact is what firmware installs, while the source repository and ref are what humans inspect.

About

Developer SDK and manifest schema for Marginalia packages.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages