Node Types and Schemas
Per-project node schemas, typed fields, the many-to-many node-to-project mapping, and the universal fields every node has.
What is a node type?
A node type defines the shape of a class of workflow nodes in your organization. Each type has a name (e.g. SimulationNode, MeasurementNode, LayoutNode) and a list of typed fields. The OptixLog backend validates payloads against this schema on ingest.
Node types are defined at the organization level and associated with projects through a many-to-many mapping: a single node type can appear in multiple projects, and a project can include many node types.
Universal fields
Every node type gets two universal fields regardless of its per-type schema:
| Field | Type | Meaning |
|---|---|---|
name | str | Human-readable name for the node. Always required. |
attached_files | list[str] | File references or file IDs to attach to the node. |
These fields are present in the generated dataclass alongside any per-type custom fields.
Per-type custom fields
Each node type defines additional fields with a name, a type, a required flag, and optionally a default value or an enum constraint.
Field types
Schema type | Python annotation |
|---|---|
string | str |
integer | int |
number | float |
boolean | bool |
json / object | Mapping[str, JSONValue] |
array<T> | Sequence[T] (recursive) |
enum | Literal[members...] |
array<T> is parsed recursively: array<number> becomes Sequence[float], array<string> becomes Sequence[str].
Required and default rules
Given a field with base type T:
required: true→name: T(must be supplied at construction).required: falsewith a default →name: T = <default>.required: falsewithout a default →name: T | None = None.
Example generated dataclass (from the sample schema):
@dataclass(frozen=True, kw_only=True)
class SimulationNode:
# Universal fields
name: str
attached_files: list[str] = field(default_factory=list)
# Per-type custom fields
config: Mapping[str, JSONValue] # required, type=json
solver: Literal["fdtd", "eme", "varfdtd"] # required, type=enum
wavelength_nm: float = 1550.0 # optional with default, type=number
other_param: str | None = None # optional without default, type=string
def to_payload(self) -> dict[str, JSONValue]: ...Many-to-many mapping
The node-to-project association is stored in the schema's project_node_types section:
{
"project_node_types": {
"proj_grating_7f3a": ["SimulationNode", "MeasurementNode"],
"proj_modulator_22b1": ["SimulationNode", "LayoutNode"]
}
}Here SimulationNode is shared by both projects — it is emitted once as a single dataclass that appears in both projects' union aliases. MeasurementNode is only in the first project; LayoutNode is only in the second.
Association is derived from the schema, not optixlog.toml
The node-to-project mapping is never stored in optixlog.toml. It is fetched from the server (or fixture) at optixlog generate time and baked into the generated bindings.
How schemas are fetched
optixlog generate fetches the schema from the server's v0.codegen.schema endpoint (or reads it from a --fixture file). It parses the schema with contract.py, maps field types with typemap.py, and emits the Python module with emit.py.
The output is byte-deterministic: identical schema input always produces identical Python output. You can safely commit optixlog_gen.py and diff it.
Accessing schema data at runtime
You do not typically need to access schema metadata at runtime — the generated bindings encapsulate it. If you do need it, use the Management API's workflow node labels collection:
for label in project.workflow_nodes.labels.iter():
print(label.id, label.name)See Node Types and Schema reference for the full model definitions.