--- id: STORE-10 family: GR-STORE level: extended profiles: [storage-api, runtime] posture: normative-target added: "1.0" changed: "1.0" source: https://flowdrop.io/spec/rules/gr-store/store-10 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # STORE-10 — Read-path repairs must never run on the save path *GR-STORE (Part I) · level: extended · profiles: storage-api, runtime · added in 1.0* Loading a workflow to run it may quietly repair it. Doing the same on the way in would hide exactly the conditions validation exists to report. ## The rule > **Normative.** This is the rule. > > 1. When a workflow is loaded to be compiled or executed, an implementation may normalise it: dropping an edge whose source or target does not resolve, minting an id for an edge that has none, filling a node's configuration so that a stored value wins over a node-type default and defaults fill only absent keys, and keying nodes and edges by id so that the last occurrence of a duplicated id wins. > > 2. None of that may happen on the save, validate or mutate path, which must see the workflow exactly as submitted or as stored. ## What it means Reading a workflow to run it is allowed to be more forgiving than reading it to save or validate it, and the rule is about keeping those two readings from sharing code. A dangling edge, a duplicated id, a node missing an edge id — compiling or executing can quietly repair all three, because a run has to proceed on the best available graph. Validation exists specifically to report those same conditions, so a repair that runs before validation sees them hides exactly what the caller needed to be told. The save, validate and mutate paths must see the workflow exactly as it was submitted or as it is stored — dangling edge, duplicate id, missing id and all. Two of the normalisations have a direction worth naming, because the wrong one is the more intuitive-sounding choice. Config filling only fills the keys an author left absent: the value the author actually saved wins over whatever default the node type now declares, never the reverse — a stale node-type default is not allowed to override real input. And when a node or edge id repeats, the last occurrence read wins, silently dropping every earlier copy under that id; a validator reading the same raw list, unrepaired, sees both copies and can report the collision the repair would have erased. ## Example A node's stored config and its node type's default disagree on one key and the default carries a second key the author never set. ```json title="The node's stored config" {"operation": "multiply"} ``` ```json title="The node type's default config" {"operation": "add", "precision": 2} ``` ```json title="What the read path resolves it to" verdict="stored wins" {"operation": "multiply", "precision": 2} ``` A workflow whose node list carries the id `a` twice reads, on the read path, as one node — the second occurrence's data, the first discarded without a trace: ```json title="Two nodes carrying the same id" verdict="last wins" [{"id": "a", "data": {"label": "first"}}, {"id": "b", …}, {"id": "a", "data": {"label": "third"}}] ``` The read path resolves this to two nodes, `a` and `b`, with `a` labelled `"third"`. Validation, reading the same three-node list unrepaired, is where a duplicate id is reported at all. ## Related rules - Names: STORE-5 --- Rule identifiers are permanent and are never renumbered. This specification carries no implementation status: each implementation publishes its own standing against these rules. Licensed CC BY 4.0.