FlowDrop Workflow Specification 1.0-draft

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

Normative: this is the rule
  1. 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.
  2. The lane is never the fragment's type.
  3. 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.
  4. 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.
  5. 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.
  6. None of this changes what a caller must pass: the launch check reads type and enum from 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.

Two outputs whose declared lane is not their JSON Schema type201 — stored
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"}]}
]}}
What the published contract reports for those two ports
{ "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:

A node type's own schema fragment for a bound port
{ "type": "array", "title": "Messages", "x-data-type": "messages" }
What the built contract stamps onto that fragmentpinned
{ "type": "array", "x-data-type": "messages" }
Rule identifiers are permanent and are never renumbered. Each implementation publishes its own standing against these rules; this specification does not.spec 1.0-draft · MAN-21 · changed in spec 1.0