RFC 0007 — Unknown entity kinds and opaque payloads
Document status: accepted (decision delegated to research on 2026-08-29; evidence in
nuif:research:unknown-schema-preservation-strategies,nuif:research:opentimelineio,nuif:research:godot-tscn-scene-format,nuif:research:openusd-composition-and-crate,nuif:research:gltf-validator-and-sample-assets). Extends RFC 0002. Canonical source.
Motivation
RFC 0002 requires unknown extension data to survive load, save and edit. It does not define how an entity whose kind is unknown is represented, which operations remain valid on it, how it lays out and renders, or how validation severities are assigned. Godot preserves unknown node classes as placeholders that record the original class and properties and write them back; OpenTimelineIO preserves unknown schemas with name, version and raw payload while still decoding nested known objects; Blender ignores and cannot re-save unknown data; OpenUSD keeps unknown typeName as metadata with fallbackPrimTypes; glTF gates by extensionsUsed and extensionsRequired with validator codes UNDECLARED_EXTENSION (error) and UNSUPPORTED_EXTENSION (information).
Decision
Representation
- An entity whose kind namespace, kind name or schema version is not supported MUST be loaded as
EntityKind::Unknown, retaining namespace, kind name, schema version and the kind-specific payload bytes. A known kind whoseschema_versionexceeds the implementation’s support MUST also load asUnknown, never fail. - Core fields (identifier, name, children,
nuif-namespace properties, relations, extensions) of an unknown entity remain typed and editable. Only the kind-specific payload is opaque. - Every containment slot MUST admit
Unknown.
Operations
Remove,Move,Rename,SetExtension,RemoveExtensionand core-property operations MUST apply to unknown entities unchanged.SetUnknownPayloadis valid only when the applying implementation declares the payload’s namespace; other implementations MUST reject it with a diagnostic.
Preservation
- An implementation that does not declare a namespace MUST preserve that namespace’s payloads byte-for-byte. An implementation that declares the namespace MAY re-encode deterministically (value-for-value). RFC 0002’s “byte/value-for-byte” language is read as these two cases.
- In
nuif-cbor-0, opaque payloads are CBOR byte strings; the declared encoding (CbororOctets) is a sibling field, not a CBOR tag (RFC 0005 rule 13 forbids tags in the canonical body). Hashing covers the bytes.nuif-text-0encodes the bytes losslessly. - A malformed opaque payload yields a diagnostic on the owning entity and MUST NOT invalidate the document.
- Lowering, flattening and codec conversion MUST carry
Unknownentities and extension payloads through; a conformance fixture asserts byte identity after an edit cycle through an implementation that declares neither the kind nor the namespace.
Evaluation
- Layout treats an unknown entity as the kind named by the document’s
fallback_kinddeclaration for its namespace, else asContainerwith its authored size intents. - Rendering of an unknown entity reports
PreservedUnrenderable { namespace, entity }and draws nothing for the kind-specific payload; children render normally.
Validation severities
- A namespace present in the document but absent from
extensions_used: error. - A namespace declared in
extensions_usedand unsupported by the implementation: information. - A namespace declared in
extensions_requiredand unsupported: blocks claims of faithful rendering and export fidelity abovepreserved_unrenderable; MUST NOT block structural editing.
Type changes (nuif-core, signatures only)
#![allow(unused)]
fn main() {
pub enum OpaqueEncoding { Cbor, Octets }
pub struct OpaquePayload { pub encoding: OpaqueEncoding, pub bytes: Vec<u8> }
pub struct UnknownKind { pub namespace: String, pub kind: String, pub schema_version: u32, pub payload: OpaquePayload }
pub enum EntityKind { /* existing variants */ Unknown(UnknownKind) }
pub struct Extensions(pub BTreeMap<String, OpaquePayload>);
pub struct ExtensionDeclarations { pub used: BTreeSet<String>, pub required: BTreeSet<String>, pub fallback_kind: BTreeMap<String, EntityKind> }
pub enum Fidelity { /* existing */ PreservedUnrenderable { namespace: String, entity: Option<EntityId> } }
}
nuif-protocol: SetExtension { entity, namespace, payload: OpaquePayload }, RemoveExtension { entity, namespace }, SetUnknownPayload { entity, payload: OpaquePayload }.
Compatibility
Existing Extensions(BTreeMap<String, Vec<u8>>) gains the encoding field; no documents exist.
Security
Payload size and count are bounded by parser limits (spec/11); payloads are never interpreted by implementations that do not declare the namespace, so untrusted content in them cannot reach an interpreter.
Conformance tests
- extensions suite: decode with an ignorant implementation, apply
Rename,Move,SetExtensionon neighbours and on the unknown entity itself, encode, assert byte identity of the unknown payload and restoration of the original kind name and version by a knowing implementation. - validation suite: fixtures for severities 12–14.
- layout suite: unknown entity with
fallback_kindand without.
Rejected alternatives
- Silent drop (pre-3.5 proto3 behaviour, Blender): the failure NUIF exists to prevent.
- Host-typed re-encoding of unknown properties (Godot
Variant): changes bytes in an ignorant implementation. - Hard failure on newer schema versions (OpenTimelineIO): prevents structural editing of otherwise valid documents.
- CBOR tag 24 for declared-CBOR payloads: tags are forbidden in the canonical body by RFC 0005, and tag 24 requires well-formed content, which rule 8 does not.
Unresolved
- Whether
fallback_kindmay name a kind from another extension namespace (transitive fallback) is deferred until a second dialect exists.