--- id: STORE-9 family: GR-STORE level: extended profiles: [storage-api] posture: normative-target added: "1.0" changed: "1.0" source: https://flowdrop.io/spec/rules/gr-store/store-9 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # STORE-9 — Import gates run in a fixed order and roll back what they generated *GR-STORE (Part I) · level: extended · profiles: storage-api · added in 1.0* Importing a bundle can create node types before it knows whether the workflow is acceptable. The fixed order, and the rollback, are what keep a refused import from leaving debris behind. ## The rule > **Normative.** This is the rule. > > 1. A bundle import applies its gates in this order: envelope format, publisher trust, capability manifest, node-type generation, workflow validation, flow-id shape, id collision. > > 2. Any refusal after node-type generation rolls the generated node types back, so a refused import leaves nothing behind. > > 3. An unsupported envelope format is refused with 422. > > 4. An untrusted publisher is refused with 403, unless the caller both confirms the publisher and is permitted to do so. > > 5. A processor the installation does not have is refused with 422, reported ahead of validation so that a missing processor is actionable rather than surfacing as a structural error. > > 6. A workflow that fails validation is refused with 422 and `details`. > > 7. An id that already exists is refused with 409, never overwriting. > > 8. A non-empty flow id must match `^[a-z0-9_]+$` and be at most 64 characters; an empty flow id is accepted and one is minted. > > 9. Every refusal names its reason: it carries at least one `details` entry identifying the cause, and a flow-id refusal locates itself at `flow.id`. > > 10. Exposure entries are normalised before validation for every bundle, a trusted one included: an entry keeps only its `name`, `node_id` and `port`, and only where those are scalar, cast to string; an entry that is not an object, or is wholly malformed, becomes empty but keeps its index, so validation reports it against its own position instead of shifting every later one. > > 11. What is stored is the entry as submitted, so an author's additional entry metadata survives the import. ## What it means Generating node types happens before the workflow is known to be acceptable, because a bundle can only be validated once the processors it describes exist to validate against. That ordering choice is what forces the rest of the rule: anything the import creates ahead of validation — a node type, most of all — has to be rolled back the moment a later gate refuses, or a refused import would leave behind exactly the debris a caller has no way to see or undo. The exposure-entry coercion runs even for a bundle whose publisher is trusted. Trust decides whether the bundle may be imported at all, not whether the entries inside it are safe to hand to the workflow validator: a malformed entry is normalised to an empty one that keeps its original position, so a validation error still names the entry it belongs to instead of shifting onto whichever entry happened to land at that index next. What is stored, though, is the entry as the bundle submitted it — the coercion protects the validator's reads, not what ends up on record. A missing processor is reported ahead of workflow validation for the same reason node-type generation runs early: without the processor, the workflow would fail validation with a wall of structural errors that all trace back to one absent capability. Naming the capability first is what makes the refusal actionable. ## Example ```http title="A bundle from an untrusted publisher" verdict="403 refused" POST /api/flowdrop/workflows/import {"format": "flowdrop.bundle/v1", "payload": {"flow": {"id": "imported_flow", "label": "Imported Flow", "nodes": [ … ], "edges": []}}} ``` ```http title="A bundle needing a processor the installation lacks" verdict="422 refused" POST /api/flowdrop/workflows/import {"format": "flowdrop.bundle/v1", "payload": {"publisher": "acme", "flow": {"id": "imported_flow", "label": "Imported Flow", "nodes": [{"id": "g.1", "data": {"metadata": {"node_type_id": "ghost", "executor_plugin": "ghost_module:ghost"}}}], "edges": []}}} ``` ```http title="A bundle whose flow id is already taken" verdict="409 refused" POST /api/flowdrop/workflows/import {"format": "flowdrop.bundle/v1", "payload": {"publisher": "acme", "flow": {"id": "imported_flow", "label": "Imported Flow", "nodes": [ … ], "edges": []}}} ``` The 409 never overwrites: the workflow already on record under that id is untouched, and nothing from the rejected bundle — including any node type it would have generated — survives the refusal. ## Why Recorded under OPEN-19. ## Related rules - Names: STORE-3, STORE-5 - Referenced by: 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.