A contract entry states the port's lane as well as its schema
A port's lane and its JSON Schema type are two different vocabularies. Answering one with the other made a contract entry contradict the port it was bound to.
The rule
- An interface entry states its bound port's lane and its structural schema separately: the lane is the port's declared or derived data-type lane, and the schema is the JSON Schema fragment.
- The lane is never the fragment's
type. - It is resolved once, when the contract is built, against the vocabulary the implementation actually serves, and is pinned into the stored fragment, so a reader sees the lane the workflow was published against rather than one that could drift since.
- Reading the contract only reads that pin; a stored contract built before the pin existed falls back to the lane derivable from the fragment's type, which is never wider than the truth, and self-corrects on the workflow's next rebuild.
- The schema emitted alongside it is the structural contract only: the pinned lane, the title, the description, the examples and property-level required flags are stripped on the way out, because the entry states each of them itself.
- None of this changes what a caller must pass: the launch check reads
typeandenumfrom the stored contract and never consults this projection.
What it means
A port's lane and its JSON Schema type are two different vocabularies, and
they diverge exactly where a port carries a domain-specific meaning over an
ordinary structural shape: a list of conversation messages is, structurally,
just an array. Reading the schema's type and calling that the lane would
answer array for a case the lane can name precisely.
The lane is resolved once, when the contract is built, and pinned into the
stored fragment — it is not re-derived on every read. A contract built
before the lane existed has no pin to read, so it falls back to the lane the
schema's type implies, which is never wider than the truth, and corrects
itself the next time the contract is rebuilt. The schema published alongside
an entry is stripped of the lane, the title, the description and the
examples on the way out, because the entry states each of those itself; none
of it changes what the launch check reads, which is still type and enum
on the stored contract, never this projection.
Example
A node produces two outputs: one declares a messages lane over a JSON
Schema array, the other declares nothing over an object.
POST /api/flowdrop/workflows
{"name": "Lane WF", "nodes": [ … ], "interface": {"outputs": [
{"id": "out_list", "bindings": [{"nodeId": "msg1", "portId": "messages"}]},
{"id": "out_one", "bindings": [{"nodeId": "msg1", "portId": "message"}]}
]}}{ "outputs": [
{"id": "out_list", "dataType": "messages", "schema": {"type": "array"}},
{"id": "out_one", "dataType": "json", "schema": {"type": "object"}}
] }The node type's own fragment carries more than the pin keeps:
{ "type": "array", "title": "Messages", "x-data-type": "messages" }{ "type": "array", "x-data-type": "messages" }