Plugin presentation contracts 0.4.0
Plugins can declare optional configuration, readings, dataset and specialised panel pages through a shared host UI. A plugin with no presentation remains usable through its descriptor. This contract and the SDK tools validate presentation candidates; they do not activate attachments in the registry. The SDK additionally bundles a labelled, simulation-only preview renderer (benchweave-sdk preview-ui) for local authoring — it displays simulated states, serves loopback only, and never operates hardware or publishes anything.
Ownership and execution
The gateway owns device connections, acquisition, retained observations, leases, approvals and run state. Pages display gateway state and submit explicit requests through approved procedures. A plot never starts its own device polling loop. Selecting a preset performs no I/O; applying it is a separate operation requiring the normal procedure and approval checks.
A browser disconnect must not terminate a gateway-owned procedure. Interactive manual control remains subject to the gateway’s lease expiry and protective transition rules. A UI manifest cannot grant permissions, bypass a lease or declare an operation that the descriptor does not implement.
The current adapter bridge supports identify and scalar read/write. Class-action and waveform fixtures below describe contracts, not newly implemented acquisition support. Dataset views require a host implementation of the corresponding descriptor action and dataset delivery.
Resource layout
Keep each distributable plugin in plugins/<vendor>/<device>/, with the developer SDK in packages/sdk/. An external plugin uses the same package layout without depending on the gateway distribution:
plugin-project/
pyproject.toml
README.md
tests/
src/plugin_package/
__init__.py
adapter.py
protocol.py
descriptor.json
vectors.json
presentation.json
binding-catalogue.json
ui/
manifest.json
settings/ # only for implemented configuration actions
configuration.schema.json
presets/ # complete, versioned configuration documents
documented-configuration.json
assets/ # optional presentation resources
The default SDK starter remains read-only and has no presentation files. --with-ui adds a readings page and catalogue while preserving its descriptor, adapter and protocol bytes. Configuration schemas and presets are added only when the plugin implements those actions. Package all declared resources, include them in the normal payload inventory and update hashes whenever their bytes change.
Four documents
| Document | Purpose |
|---|---|
presentation-envelope.schema.json |
Candidate attachment: contract version, descriptor hash, package-relative resource root and manifest path/hash. |
ui-manifest.schema.json |
Optional pages, typed bindings, assets, plots and required host features. |
binding-catalogue.schema.json |
Host-provided descriptor binding metadata, including action/schema relationships and observation/dataset variables. |
configuration-preset.schema.json |
Complete settings, plugin/profile/firmware compatibility, settings schema identity/hash and provenance. |
Each schema has an exact 0.4.0 identifier. Fields are closed except explicitly namespaced manifest extensions. The three smaller schemas refer to shared definitions in the manifest schema; validators resolve them from the local corpus. This directory is the canonical machine corpus; its bytes are pinned row-by-row by standards/corpus-manifest.json. This version supersedes 0.3.1 (digest-frozen, still in the corpus); OTDP 0.2.3 and adapter API 1.1 remain unchanged; the registry contract is 0.1.3.
IDs within pages, bindings, targets, variables and assets must be unique. A binding identifies a catalogue target and retains its kind. Observation targets identify readable descriptor parameters and compatible units/types. Action targets identify a declared action and the profiles which supply it. The profiles come from the trusted-host corpus, or from the descriptor’s digest-pinned contract documents that the host admits and merges through the composition guard. A profile id declared by two documents with different rows makes the binding ambiguous. The validator refuses it with profile_shadow on each target that binds it; identical rows are benign. A pinned contract document must not embed a $id that equals a served schema $id (its own, or an action schema’s); the composition refuses it with pinned_id_shadow. Dataset schema identities must appear in the descriptor’s admitted contract references.
Every preset-shaped asset the manifest declares must be claimed by a configuration target’s preset_asset_ids: an unclaimed one is refused with unreferenced_preset rather than validated against guessed wiring (the author wires it — which validates it fully — or deletes it). One residual: an unclaimed asset whose bytes exceed the JSON document limit is skipped by the shape probe rather than refused — over-limit content is never preset-shaped to the validator. Binding exposure of a declared preset is a UI choice; a valid declared-but-unlisted preset passes. The catalogue is trusted host metadata, not an authority granted to plugin-supplied UI files. Pinned contract documents are caller-verified additions to the served set: the host verifies their digest against the descriptor pin and their catalog shape; validation gives them profile supply only — no schema authority. A pinned document cannot shadow a served $id. In particular, generic measurement schemas do not determine a particular dataset’s variable shapes or units. The host supplies that metadata after admission. The SDK can check an authoring catalogue’s structural consistency; it cannot certify its provenance or establish hardware conformance.
Configuration presets
Presets have explicit identities, revisions, compatible plugin/profile IDs and firmware versions, a settings schema ID and SHA-256 digest, complete settings, and author/revision/evidence provenance. Evidence is labelled synthetic or documented. Under the listed identity policy, unknown firmware or a firmware mismatch fails preset validation. The firmware gate divides authority with the descriptor’s identity policy: a firmware list the descriptor declares is enforced regardless of policy (a listed descriptor must declare one), while under firmware_policy: "commissioned" the bench’s commissioned record — not the descriptor — is the firmware authority, so a preset authored against a listless commissioned descriptor admits a missing firmware value offline and the apply-time established-firmware gate governs; the preset’s own supported_firmware list always applies to any supplied value.
No defaults are inserted, and no inheritance or merge is performed. Settings must satisfy the supplied schema. The canonical action input schema and the descriptor action’s input constraints are also applied, AND-wise, in both SDK lanes. In check-preset (lane 1, since 0.2.0) the action is resolved from the preset’s own settings-schema identity: when the schema’s $id is a corpus action input-schema $id, that action’s envelope applies to the default invocation; --action forces a named action (an action absent from the descriptor is refused as unresolved_reference, never silently skipped). A settings schema carrying a custom $id resolves to no action: lane 1 then applies no envelope and says so in its success message — the full envelope coverage for such presets is lane 2’s job (or the explicit flag). A permissive plugin schema cannot relax action constraints. There is no approval, execution or credential field in the preset contract.
The input trio (new in 0.4.0)
Three components join the presentation inventory: text-input, select and boolean-input (census keys bw-text, bw-select, bw-boolean). A control is schema-derived, not manifest-declared: a type: string field without a pattern renders text-input; an enum field renders select — its options are the enum’s members, and a const (or single-option enum) renders the read-only fixed display, never an editable offer; a type: boolean field renders boolean-input. All three stage intent — the §E.1 required text is the family’s one staging sentence: Staged value; use Apply to request the change. An absent schema bound renders the absent attribute, never an invented limit (the numeric row’s own A02 rule). Energy-removing actions never take a staged-input pattern; the trio are type=staged components.
Digital lanes plots (new in 0.3.0)
A plot of kind digital_lanes presents a logic capture: up to 64 y channel ids, each resolving to a dtype: "logic" fetch variable carrying the four-state alphabet "0","1","x","z" verbatim. The time base: the fetch dataset’s regular axis (coordinates.start/step) — the catalogue’s author-declared x variable (number, unit s, vector) names exactly that axis (t_i = start + i·step — derivation, not fabrication; the derivation is stated here as the authoring rule). The trigger marker is the dataset’s trigger.time_relative_s (null means no marker renders and no position is fabricated).
channel_hintsfor this kind carryvisibleonly —color_roleis refused by the schema (colour carries nothing in a lanes view; identity is position).lane_groups(≤16):{id, label?, member_ids (2..64), radix? ("hex" default, "decimal" opt-in), default_collapsed?}— a group may render collapsed as one bus lane; member order is declaration order with the first declared member the LSB (bus values are a pure function of (member states, member order)).decoder_lanes(≤8):{id, label?, decoder, settings?, source_channel_ids (1..4), binding_id}—binding_idnames a manifest binding on this page whose target is a dataset on ANOTHER action than the plot’s capture (the decode event_log target; OTDP classes separate fetch from decode). That other-action rule is what the validator enforces (unresolved_referenceotherwise); the target being the decode action’sevent_logshape itself is authoring intent verified at rendering, not a validation-layer check.- The analog kinds (
time_series,waveform) cannot declarelane_groupsordecoder_lanes(schemaif/then); their per-kind cap is 16 channels and hints, whiledigital_lanesadmits 64 of each.
Optional plotting and panels
A readings page may have no plots. Time-series plots require numeric scalar variables and a receipt-time axis measured in seconds. Waveform plots require numeric vector axes. Plot bindings must belong to their page and resolve to observation or dataset targets. String or boolean values remain suitable for readings tables but cannot become numeric plots.
A plot may carry channel_hints, an optional array of per-channel presentation preferences keyed by variable_id — each entry naming a colour role (accent or muted) and/or a visible flag, at least one of the two; the count ceiling is per-kind (16 for time_series and waveform, matching those kinds’ y ceiling, 64 for digital_lanes). Hints are preferences, not commands: a host composites them under its own theme authority and remains free to disregard them, and a hint can never supply a literal colour, reassign severity colouring, alter axis or tooltip theming, or introduce a channel the plot does not declare. Every variable_id must be a member of that plot’s y array (a variable of the bound target that the plot does not plot is refused as unresolved_reference, and a duplicate entry as invalid_document). Hosts MUST NOT declare or require a UI feature for consuming hints: a host with no hint-aware rendering validates and serves a hint-bearing document identically to one without hints, and omitting hints entirely changes no validation result. Superseded- and hidden-channel disclosure is a rendering concern; validation only checks shape, membership and duplicates.
Class and custom panels name an exact versioned host panel, such as vendor-panel/1.0.0. The validator never imports or executes that panel. An unavailable optional panel is returned in unavailable_pages; an unavailable required panel produces panel_unavailable and fails validation. Missing required UI features fail with unsupported_feature.
SDK commands
benchweave-sdk new example --package example_plugin --with-ui
benchweave-sdk check-ui example/src/example_plugin/presentation.json \
--descriptor example/src/example_plugin/descriptor.json \
--resources example/src/example_plugin \
--catalogue example/src/example_plugin/binding-catalogue.json \
--firmware 1.0.0
benchweave-sdk check-preset configuration.json \
--descriptor descriptor.json --settings-schema configuration.schema.json \
--firmware 1.0.0
benchweave-sdk inventory prepared-package--resources identifies the package directory; the envelope’s resource_root is resolved within it. Resource names are portable relative paths. The filesystem reader rejects symlinks, non-regular files and oversized content, including symlinks in parent components. Use canonical directory paths on systems with aliases such as macOS /var or /tmp. --feature and --panel can be repeated to describe the target host’s supported UI features and panels.
benchweave_sdk.presentation.validate_preset and validate_presentation expose the same offline checks as the CLI. SDK wheels and self-contained source archives bundle the gateway’s pure validator byte-for-byte; installed SDK operation does not import the gateway. The source checkout has a development-only loader for that same file. Neither distribution depends on a third presentation package.
The gateway’s benchweave.presentation.admission.validate_attachment accepts an already admitted descriptor, resources verified from the exact envelope root, the trusted catalogue and the host’s feature/panel sets. It returns compatibility findings and never performs package admission or activation. Registry linkage and runtime rendering are separate integration work; placing files in a folder does not attach a UI.
Limits and diagnostics
JSON documents are limited to 262,144 bytes and 32 levels of nesting, with strict UTF-8, duplicate-key rejection and finite numbers. Limits also include 64 pages, 256 bindings, targets or assets, 256-character IDs/titles, 16 MiB per resource and 64 MiB for the resource collection. Digests apply to exact original bytes, including whitespace. Schema resolution is offline.
Reports contain stable diagnostic codes and paths rather than settings values. Codes include invalid_document, limit_exceeded, invalid_schema, digest_mismatch, identity_mismatch, incompatible_firmware, unresolved_reference, capability_mismatch, invalid_settings, invalid_plot, unsafe_path, unsupported_version, unsupported_feature, unreferenced_preset and panel_unavailable. Success means offline compatibility only, not admission or approval to apply settings.
Executable synthetic examples
The SDK-generated read-only starter and tests/unit/test_presentation_manifest.py exercise readings with optional time-series plotting. tests/unit/test_presentation_specimens.py constructs a DC supply configuration preset and an oscilloscope waveform from the existing OTDP class fixtures. These fixtures are synthetic contract evidence, not qualified device drivers. scripts/sdk_smoke.py builds both distributions, rebuilds wheels from source archives, installs the generated plugin outside the checkout and checks resource hashes and SDK/gateway validator parity on the existing Linux/macOS CI matrix.