Skip to content

SRD-089.G — BPMN import: the data flow

Field Value
Status Accepted
Date 2026-08-17
Owner Ruslan Gabitov
Implements ADR-024 v.4 §2.9 (the element set), §2.14 (the report contract)
Upstream SAD-001 v.1.1 §14.1 (translation rule 2, the single-set collapse, the no-wait row), ADR-011 v.7 §2.1–§2.5 (the item-aware model, the single set, fail-fast, the three association shapes, events), ADR-030 v.1 §2.3/§2.6 (the association model both element kinds share), ADR-024 §2.16 (which refusals are staged, capability-blocked or standing)
Related SRD-089.F (the elements this stage wires; §4.9 is this document's opening premise), SRD-063 (scope-routed association traffic; §10.3 backlogs the transformation), SRD-068 (FR-4: store-routed association traffic), SRD-089.A (the dispatch tables and refSite)
Tracking #284 — Part of

SRD-089.F imported the data elements — what a definition declares. This stage imports the data flow: <ioSpecification> with its <dataInput>, <dataOutput>, <inputSet> and <outputSet>, and the two association kinds that move values between the declarations and the nodes.

The split was made because the flow is not a table. An association cannot be wired unless the parameter and the data element were built over the same item id — SRD-089.F §4.9 recorded the constraint so this document would not rediscover it — and reconciling that identity across two element families is this stage's central analysis (§4.3).

§1 Background (verified)

The whole family is refused today, staged, naming this document. No parser table claims any of the seven tags, so the zero-value disposition refuses them (dispatch.go:53-58: "Absence from the tables can therefore never mean silent acceptance"), and stagedNotes (importer.go:1775-1789) makes every such refusal carry "lands with SRD-089.G" (SRD-089.F §4.9, T-24). Two of the seven have no sections row: inputSet and outputSet are absent from the §-pin table (dispatch.go:215-220 pins the other five at §10.4.1), so their refusals name the plan but no spec section — the exact gap SRD-089.F §1 described for itemDefinition and closed with FR-8. FR-6 closes it for these two.

The model's parameter side exists and is construction-only. A data.Parameter substitutes both DataInput and DataOutput (io_spec_obj.go:47-52); NewParameter(name, iae, opts...) refuses an empty name, a reserved-character name and a nil ItemAwareElement (io_spec_obj.go:87-104); the two ParameterOptions are Optional() and WhileExecuting() (io_spec_obj.go:61,71) — the latter's own doc says "its runtime evaluation is not yet implemented; the flag only records the intent". An activity takes parameters through exactly one door, activities.WithParameters(dir, params...) (activity_options.go:190), at construction: createIOSpecs derives the InputOutputSpecification from the accumulated config (activity_options.go:75-99). There is no post-construction way in, and activities.WithoutParams() — set today by the UserTask builder — "ignores any WithParameters options" (activity_options.go:220-231).

The model has no Set type, by decision. ADR-011 v.7 §2.2: an activity has exactly one input set and one output set, "an activity's input set is its list of DataInput parameters", the per-set distinctions survive as the per-parameter optional / whileExecuting flags, and "multiple I/O sets are an explicit non-goal". SAD-001 v.1.1 §14.1 carries the same row and its consequence: "InputOutputBinding collapses to an implicit identity binding". InputOutputSpecification.Validate checks exactly one thing — per-direction name uniqueness (io_spec.go:72-81).

The association side exists, wired FROM the data element. The model's attach path is not an activity option: DataObject.AssociateSource(n, sourceIDs, transformation) and .AssociateTarget(n, transformation) (data_object.go:90-135, :139-182) — and the same two methods on DataStoreReference, which additionally seed data.WithDataStoreRef (data_store_reference.go:121-168, :173-219) — build the data.Association and bind it into the node through flow.AssociationSource / flow.AssociationTarget (flow/data_object.go:22-41), which task implements (task.go:515,520,570,575,586-587). Both directions match the node's parameter by item-definition identity: iae.ItemDefinition().ID() == do.ItemDefinition().ID() (data_object.go:151-161; the source direction at :105-108; the store variants at data_store_reference.go:138-145, :188-195).

The association's own guards implement the spec's rule 3. asscConfig refuses more than one source without a transformation — "association could have only 1 source without transformation" (data_options.go:183-194) — and WithSource refuses a duplicate source item id (data_options.go:158-165). Sources are keyed by item-definition id (data_options.go:108-110).

The runtime already consumes all of it. track.loadIncomingData / uploadOutgoingData (track.go:1711-1739) drive task.LoadData / UploadData, which gate on data.RequiredItemIDs(t.IoSpec.InputSet()) (task.go:98, :475), match each association to a parameter by item id (task.go:106), and route by Association.DataStoreRef() — store traffic to the engine registry (SRD-068 FR-4, task.go:119, :373-427), everything else through the per-instance scope by the association's names (SRD-063 FR-5, task.go:289-335). The import writes declarations this machinery already reads; nothing at runtime is built here.

Transformation exists in the model and is dead on the task path. data.WithTransformation builds it (data_options.go:116) and Association.calculate would evaluate it (association.go:280-315) — but the scope-routed task path does a plain copy and says so: "(Transformation/ assignment is a noted follow-up — SRD-063 §10.3.)" (task.go:252-253, :287-288). SRD-063 §10.3 backlogs it in the same words. <assignment> has no model at all: the Assignment interface is commented out wholesale (association.go:342-367).

Events and the process cannot take this stage's output. A catch event holds dataOutputs + outputAssociations and a throw event the mirror (event.go:279-289, :581-585), but the only writers are the message triggers (event.go:368-389, start_options.go:197-224, end_options.go:149-176); no option and no Associate* path exists, and events implement neither flow.AssociationSource nor flow.AssociationTarget. The process has no I/O carrier at all — no InputOutputSpecification, no parameter, no association field anywhere in pkg/model/process/. ADR-011 v.7 §2.5 schedules the process-level Start/End special case "with the messaging/call-activity work that needs it".

The importer's item copies already preserve identity. Every data element gets its own ItemDefinition instance under the original's id (copyItem, dataobject.go:391-413; the doc comment names this stage as the reason). So a <dataInput itemSubjectRef="idOrder"> and a <dataObject itemSubjectRef="idOrder"> meet the model's identity match with no further work — the reconciliation problem is confined to the elements that name no itemSubjectRef, which itemFor gives emptyItem(ownID) (dataobject.go:362-364): an item id equal to the element id, which matches nothing else's.

The UserTask builder must change. It passes WithoutParams() plus one placeholder renderer output because "this slice carries no ioSpecification" (dispatch.go:603-617) — and WithoutParams discards any WithParameters this stage adds. The renderer-side WithOutput is a different mechanism (user_task_options.go:210-226, a bpmncommon.ResourceParameter, not a data.Parameter) and stays.

The extract pins the family. semantics/data.md: DataInput/DataOutput Tables 10.59/10.60 and containment ("Only Tasks and CallableElements … MAY define DataInputs/DataOutputs"; embedded sub-processes may not, except through MI) (:93-113); InputOutputSpecification Table 10.58 (:115-126); InputSet/OutputSet Tables 10.61/10.62 with the empty-set meaning and the membership constraint "MUST NOT reference DataInputs not listed in dataInputRefs" (:128-161); DataAssociation Table 10.63 with the type constraint — "ItemDefinition of sourceRef and targetRef MUST match, OR the DataAssociation MUST have a transformation" — and execution rule 3's "Only ONE sourceRef" (:163-209); the activity data lifecycle (:211-241); event associations (:243-264). elements/data.md carries the per-element structural tables (:160-385).

§2 Requirements

Functional

FR-1 — <ioSpecification> on an activity imports as its parameters. Each <dataInput>/<dataOutput> becomes a data.Parameter named by its name (its id when unnamed), carrying the item its itemSubjectRef resolves to (§4.2), delivered through activities.WithParameters at construction. An <ioSpecification> with no data inputs means the activity requires no data to start, and imports as exactly that — empty parameter lists, not a refusal.

FR-2 — one <inputSet> and one <outputSet> import as flags, more refuse. The single set's optionalInputRefs / whileExecutingInputRefs (and the output mirrors) become the named parameters' Optional() / WhileExecuting() options. A set member that names an undeclared parameter is a dangling reference and refuses. A second <inputSet> or <outputSet> refuses as a standing boundary: multiple sets are ADR-011 v.7 §2.8's explicit non-goal, and the refusal says what the engine models instead.

FR-3 — <dataInputAssociation> and <dataOutputAssociation> on an activity wire the element to the node. targetRef (input) / sourceRef (output) resolve to the activity's own parameter; the other end resolves to a <dataObject>, a <dataObjectReference> (retargeting to its object, SAD-001 v.1.1 §14.1 rule 2) or a <dataStoreReference>. The wire is the model's own AssociateTarget / AssociateSource on the resolved element, so store routing and scope routing come from SRD-068 FR-4 / SRD-063 FR-5 untouched. Dangling and wrong-kind ends refuse with the converter's reference messages.

FR-4 — the item identity the model matches on is reconciled at construction. A parameter whose <dataInput>/<dataOutput> names no itemSubjectRef and is an association end adopts the item of the data element at the association's other end (§4.3). Two typed ends whose items differ refuse, citing the standard's own type constraint. A data element never adopts (§4.3 says why). Two parameters of one direction sharing an item id refuse (§4.3a): the model addresses a node's parameters by item-definition id everywhere — the association match, the readiness gate, the load loop — so a duplicate is not two parameters but one parameter declared twice, and any association naming the second would silently wire the first.

FR-5 — what the model cannot carry refuses by its ADR-038 class, none silently. <transformation> and multi-source associations (capability follow-up: SRD-063 §10.3), <assignment> (no model), a <property> end (no Associate* on Property), an <ioSpecification> on a <process> (no process carrier; ADR-011 v.7 §2.5), and data associations on events (no attach API) — each refusal names its capability or its plan (§4.6, §4.7). An <ioSpecification> on an embedded <subProcess>, <transaction> or <callActivity> refuses citing the standard's own containment rule (§4.7a) — the standard's refusal, not the engine's.

FR-6 — inputSet and outputSet gain the sections rows they never had (§10.4.1), so a set written where no table claims it is refused with its §.

Non-functional

NFR-1 — No pkg/model change. Every wire uses WithParameters, NewParameter, Optional(), WhileExecuting(), AssociateSource, AssociateTarget — all existing. If implementation finds otherwise, the capability lands first under its own document (ADR-024 §2.16).

NFR-2 — Diff-coverage ≥95% on this stage's lines, measured by make cover-check after each milestone is committed.

NFR-3 — No fabricated wiring. The converter never invents a parameter, an association or an item the document did not describe. (The one standing synthetic — the UserTask renderer placeholder — is renderer plumbing, not IOSpec content, and §4.5 narrows it rather than widening it.)

NFR-4 — No converter-local copy of a model rule. Parameter-name uniqueness, the single-source rule, duplicate-binding — the model's checks surface with the file's ids attached; the converter re-implements none.

NFR-5 — Every standard-claim is pinned to the vendored extract or a repo file:line, and every § the code emits is one the extract supports.

§3 Models

// paramSpec is a <dataInput> or <dataOutput> as read from an
// <ioSpecification>. Which direction it is comes from the tag, kept as
// the data.Direction the model already names.
type paramSpec struct {
    id, name string
    itemRef  string   // itemSubjectRef
    dir      data.Direction
    docs     []docSpec
    // optional and whileExecuting arrive from the SET's ref lists, not
    // from the parameter's own attributes (§4.4).
    optional       bool
    whileExecuting bool
}

// ioSpec is one activity's <ioSpecification> as read: its parameters and
// the one set per direction the engine models. Sets beyond the first
// refuse at parse (FR-2).
type ioSpec struct {
    params []paramSpec
    // setSeen tracks that at most one set per direction was read.
    setSeen map[data.Direction]bool
}

// dataAssocSpec is one data association as read from an activity's body:
// its own id, the activity-side parameter reference, and the data-element
// side. (The name avoids the .E assocSpec, which is the artifact
// <association>.)
type dataAssocSpec struct {
    id string
    // dir tells which tag it was: Input (dataInputAssociation) or
    // Output (dataOutputAssociation).
    dir data.Direction
    // paramRef is targetRef on an input association, sourceRef on an
    // output one — the end that names the activity's own parameter.
    paramRef string
    // elemRef is the other end: the data element in scope.
    elemRef string
    // transformation is the <transformation> body if present — refused,
    // never mapped (§4.6), kept only so the refusal can quote position.
    hasTransformation bool
    // extraSources are sourceRefs beyond the first on an input
    // association — refused with the transformation rule (§4.6).
    extraSources []string
    docs         []docSpec
}

nodeBody gains io *ioSpec and dataAssocs []dataAssocSpec. The association pass runs from build after buildDataElements, over the assembled nodes and data elements.

§4 Analysis & decisions

§4.1 Parameters ride the body, associations ride a pass

The two halves of the family land in the two places the pipeline already has for exactly these shapes.

A parameter is construction inputWithParameters is the one door (§1) — so <ioSpecification> joins nodeChildParsers and fills nodeBody, the same children-before-constructor order .C built for <script> and .D for the event definitions: a child that decides what is constructed cannot follow the construction. The parameters are built in pass 2 (buildNode), where the item map exists.

An association is cross-element wiring — both its ends must already be built, and the container holding them is itself a node — so it lands as a pass after buildDataElements, exactly where placeLaneNodes sits for the same reason (lanes name nodes; associations name nodes and data elements).

§4.2 A parameter's item comes from the same itemFor

<dataInput itemSubjectRef="idOrder"> resolves through the itemFor that serves every item-aware element (SRD-089.F §4.2): a declared item is copied per element under its original id; an unresolvable reference was already reported at the item; a missing itemSubjectRef gets emptyItem(paramID)unless §4.3 reconciles it. The three refusals (not declared / a flow element / a catalog object) arrive with the parameter named as the referrer, unchanged.

§4.3 Identity reconciliation: the parameter adopts, the element never does

The model matches an association to a node parameter by item-definition id (§1). The standard makes the same demand in its own vocabulary: source and target ItemDefinition "MUST match, OR the DataAssociation MUST have a transformation" (semantics/data.md:181) — and the transformation is refused here (§4.6). So the imported pair must genuinely share an item id, and there are three cases:

  1. Both ends typed. They match (the file is well-formed) — nothing to do; the .F item copies preserved the shared id (§1). They differ — the file violates the standard's type constraint, and the refusal cites §10.4.1 and both ids rather than the model's later "has no input" message, which would send the modeler hunting a missing parameter that is not missing.

  2. The parameter is untyped, the element is typed (the common export shape — modelers type the object, not the port). The parameter adopts the element's item: it is built over a copy carrying the element's item id, so the model's match holds by construction. This invents no data (NFR-3) — the standard itself says an untyped end has no ItemDefinition to violate, and the adoption is exactly what "takes its type from" means.

  3. The element is untyped (with a typed or untyped parameter). The element does not adopt: a data object may feed several nodes through several associations, and adopting from one association would make the second association's type a function of build order — the converter would be choosing a type the file never wrote, differently depending on document order. Both sub-cases refuse, telling the modeler to give the data element an itemSubjectRef. The cost — a fully-untyped file refuses at its first association — is accepted: the alternative is order-dependent silent typing, which is the class of bug this converter exists to refuse.

Adoption happens at parameter construction (pass 2, buildNode), which needs the association specs readable before the nodes are built — they are: pass 1 collected them into the node's own body (§4.1), so the builder can look the parameter's associations up without a new pass. One mechanical consequence to state, because build order hides it: the data element at the other end is not built yetbuildDataElements runs after buildNodes (the container holding a data object IS a node, SRD-089.F §4.4) — so adoption resolves the partner's item through its parse spec (asm.datas → the spec's itemRefasm.items), never through a built element. The specs are complete by pass 2, so nothing is missing; it is only the objects that come later.

§4.3a A duplicate item id in one direction is one parameter twice

The model has no per-parameter address besides the item id: the association match (data_object.go:151-161), the readiness gate (RequiredItemIDs, io_spec_obj.go:155-… — a map keyed by item id) and the load loop (task.go:106) all resolve a node's parameter by ItemDefinition().ID(), and each takes the first match. Two <dataInput>s naming one itemSubjectRef therefore do not import as two parameters — the runtime would merge them in the gate and mis-wire every association to whichever the slice holds first, silently.

So the import refuses the duplicate, telling the modeler to declare one <itemDefinition> per parameter. The parameter-name uniqueness check the model does have (io_spec.go:72-81) does not cover this — two names, one item — which is why the refusal is the converter's to make (a documented narrowing, not a copy of a model rule; NFR-4 is honored because the model has no rule here to copy). Untyped parameters are immune by construction: emptyItem(paramID) gives each its own id — but two untyped parameters adopting from the same data element (§4.3 case 2, twice) collide the same way and refuse with the same message.

§4.4 The set flags are per-parameter facts, and the sets vanish

ADR-011 v.7 §2.2 keeps the standard's within-set distinctions "as per-parameter attributes rather than set membership", and the model's two ParameterOptions are exactly the two lists: optionalInputRefsOptional(), whileExecutingInputRefsWhileExecuting() (§1). The import reads the single set, flags the named parameters, checks the membership constraint the extract states ("MUST NOT reference DataInputs not listed in dataInputRefs", semantics/data.md:137-140) as a plain dangling-reference refusal, and builds no Set object — there is none to build, and SAD-001 v.1.1 §14.1 already collapsed InputOutputBinding to identity for the same reason.

WhileExecuting's runtime is not implemented (its own doc says so, §1) — but the flag is the model's declared way to record the intent, so importing it maps document content onto model content and is not a lie. No report is emitted for it: the model carries what the file said.

A second set per direction is a standing refusal (FR-2): ADR-024 §2.16's decided-non-goal class, because ADR-011 v.7 §2.8 rules multiple sets out by choice ("genuine alternative input/output modes are modelled with gateways or boundary events" — SAD-001 v.1.1 §14.1). The refusal says that, and names no issue (a standing boundary "is not a defect and must not be re-filed as one").

§4.5 The UserTask placeholder narrows to the file's silence

Today every imported UserTask gets WithoutParams() plus a placeholder renderer output (§1). After this stage the placeholder pair applies only when the file declares no <ioSpecification> — a file that declares one gets WithParameters and no WithoutParams (which would discard them, §1). The renderer-side WithOutput("result", ...) remains in both branches: it feeds bpmncommon.NewResource's non-empty-list demand and is not IOSpec content. The synthesized placeholder remains "model plumbing, not BPMN content" and is still not written back on export — the exporter writes nothing from the data family at all (exporter.go, grep-clean).

§4.6 Transformation, assignment, multi-source: refused as the follow-up they are

An association with a <transformation> imports into a model that would silently not evaluate it on the one path this stage can wire — the task path does a plain copy and its own comment defers the feature to SRD-063 §10.3 (§1). Importing the expression would turn a declared mapping into a silent no-op at runtime, which is precisely the quiet loss ADR-024 v.4 §2.14 exists to prevent. So it refuses, capability-blocked: the refusal names the capability (association transformation evaluation on the scope path, SRD-063 §10.3) and the issue that tracks it (§4.8).

Multi-source associations follow: the standard permits several sources only under a transformation (rule 3, semantics/data.md:185-192), the model refuses exactly that combination ("only 1 source without transformation", §1), and with the transformation refused there is no legal multi-source left. One refusal wording covers both, so the modeler reads one fact, not two.

<assignment> has no model — the interface is commented out (association.go:342-367) — same class, same issue, its own wording (an assignment is not a transformation, and telling a modeler to convert one into the other would be advice the engine cannot honor either).

§4.7 The ends the model cannot reach: events, the process, properties

Three legal BPMN shapes have no model attachment path (§1):

  • Data associations on events — the fields exist, no API fills them from outside the events package.
  • <ioSpecification> on a <process> — no carrier at all; ADR-011 v.7 §2.5 schedules the process-level Start/End data path "with the messaging/call-activity work that needs it".
  • A <property> as an association end — the standard allows it ("sources are item-aware elements accessible in current scope (DataObject, Property, Expression)", semantics/data.md:167-169), but Associate* exists only on DataObject and DataStoreReference.

All three refuse capability-blocked, each naming its missing capability and its tracking issue (§4.8) — not staged (no landing document schedules them) and not standing (nothing decided against them; ADR-011 §2.5 explicitly plans one of them).

A locus note the uniform parse forces: nodeChildParsers serves every flow node from one table, so an event's <dataInputAssociation> — or a gateway's <ioSpecification> — is parsed like a task's. The refusal therefore fires in pass 2, at buildNode, where the node's kind is known; parse-time cannot tell the owners apart and should not try (a second table is the drift .C's one-table rule exists to prevent).

§4.7a Containment: the owners the standard itself refuses

Superseded in part. The <callActivity> left this set with SRD-096: §10.4's own CallActivity row maps its DataInputs/DataOutputs onto the callable's, which presupposes it has them, and reading §10.4.1's containment list strictly made that row unreachable — no imported document could hand data to a callable at all. The sub-process, gateway and event refusals below are unchanged, and they are the ones the standard genuinely makes.

The extract's containment rule — "Only Tasks and CallableElements (Processes, GlobalTasks) MAY define DataInputs/DataOutputs (via their InputOutputSpecification). Embedded SubProcesses MUST NOT define DataInputs/DataOutputs directly — but MAY do so indirectly via MultiInstanceLoopCharacteristics" (semantics/data.md:96-98) — cuts one more slice: an <ioSpecification> on an embedded <subProcess>, a <transaction> or a <callActivity> is a shape the standard refuses, not the model. It must not import silently — a sub-process takes ActivityOption, so without a guard the parameters would land — and it must not read as a converter gap either. It refuses citing the containment rule and its §, the same class as a <dataStore> written inside a process (SRD-089.F T-25a): a legal element in an illegal place.

The MI indirection the clause permits (loopDataInputRef) is the MI family's business and is not touched here; ADR-030 v.1 §2.8 already records how a collection object feeds it.

Task kinds import parameters; everything else refuses at buildNode — containment-refused for the activity kinds the standard excludes, capability-blocked for the events BPMN allows and the model cannot attach (§4.7).

§4.8 The ADR-038 register gains four rows

ADR-024 §2.16 is the register of missing capabilities, one issue each. This stage adds four, created at authoring so the refusal wording carries real numbers:

Missing capability What it unblocks Tracking
transformation/assignment evaluation on the scope-routed association path (SRD-063 §10.3's follow-up) <transformation>, <assignment>, multi-source data associations #328
an attachment API for event data associations data associations (and bare I/O) on catch and throw events #329
a process-level I/O carrier (ADR-011 v.7 §2.5's planned work) <ioSpecification> on a <process>, the Start/End data path #330
Associate* on Property a <property> as a data-association end #331

The property end gets its own row rather than riding #328: the register wants one row per capability, and "a Property can be an association end" is a different model change from "the copy engine evaluates expressions". The ADR bump is part of this stage's landing, not a separate branch.

§4a Worked example

<bpmn:definitions xmlns:bpmn="…" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <bpmn:itemDefinition id="idOrder" structureRef="xsd:string"/>
  <bpmn:process id="P" name="P">
    <bpmn:dataObject id="do1" name="order" itemSubjectRef="idOrder"/>
    <bpmn:startEvent id="s1"/>
    <bpmn:task id="t1" name="Work">
      <bpmn:ioSpecification id="io1">
        <bpmn:dataInput id="din1" name="in" itemSubjectRef="idOrder"/>
        <bpmn:dataOutput id="dout1" name="out"/>
        <bpmn:inputSet id="is1">
          <bpmn:dataInputRefs>din1</bpmn:dataInputRefs>
          <bpmn:optionalInputRefs>din1</bpmn:optionalInputRefs>
        </bpmn:inputSet>
        <bpmn:outputSet id="os1">
          <bpmn:dataOutputRefs>dout1</bpmn:dataOutputRefs>
        </bpmn:outputSet>
      </bpmn:ioSpecification>
      <bpmn:dataInputAssociation id="dia1">
        <bpmn:sourceRef>do1</bpmn:sourceRef>
        <bpmn:targetRef>din1</bpmn:targetRef>
      </bpmn:dataInputAssociation>
      <bpmn:dataOutputAssociation id="doa1">
        <bpmn:sourceRef>dout1</bpmn:sourceRef>
        <bpmn:targetRef>do1</bpmn:targetRef>
      </bpmn:dataOutputAssociation>
    </bpmn:task>
    <bpmn:endEvent id="e1"/>
    <bpmn:sequenceFlow id="f1" sourceRef="s1" targetRef="t1"/>
    <bpmn:sequenceFlow id="f2" sourceRef="t1" targetRef="e1"/>
  </bpmn:process>
</bpmn:definitions>

yields:

  • t1's IOSpec: one input in over item idOrder, optional; one output out whose parameter adopted idOrderdout1 names no itemSubjectRef and its association's other end is do1 (§4.3 case 2).
  • Two associations on t1: the input one from do1 (scope-routed, SRD-063 FR-5), the output one to do1 — both wired through the data object's own Associate*, matching on idOrder.
  • res.Dropped is empty: everything in this file maps.
  • The process runs: the engine's LoadData finds the input by item id, the scope serves order, and UploadData writes the task's output back — the same machinery §1 verified, fed for the first time from XML.

§5 API deltas

None in pkg/model (NFR-1). In pkg/convert, none — the family's refusal wording changes are message text on the existing UnsupportedElementError.Planned / classified-error paths.

§6 Test scenarios

# Scenario Asserts
T-1 <ioSpecification> with one typed input and one typed output both parameters exist, right names, right items, right directions
T-2 unnamed <dataInput> parameter named by id
T-3 empty <ioSpecification> (sets only, no params) imports; empty parameter lists; no refusal
T-4 optionalInputRefs / whileExecutingInputRefs members IsOptional() / IsWhileExecuting() on exactly the named parameters
T-5 a set ref naming an undeclared parameter refused with the converter's reference message
T-6 two <inputSet>s refused, standing wording (gateways/boundary events named, no issue, no "yet")
T-7 input association dataObject → dataInput, both typed idOrder wired; LoadData gets the value (e2e below)
T-8 output association dataOutput → dataObject wired through AssociateSource
T-9 association via <dataObjectReference> retargets to the object (SAD-001 §14.1 rule 2)
T-10 association with a <dataStoreReference> end wired; the association carries DataStoreRef()
T-11 untyped parameter, typed element parameter adopts the element's item (§4.3 case 2)
T-12 typed↔typed mismatch refused citing §10.4.1's type constraint, both ids named
T-13 untyped element end refused, the wording telling the modeler to type the element (§4.3 case 3)
T-14 dangling sourceRef/targetRef; wrong-kind ends the converter's notFound / wrongKind messages
T-15 <transformation> present refused capability-blocked, naming SRD-063 §10.3's follow-up and its issue
T-16 two <sourceRef>s, no transformation the same refusal wording (§4.6)
T-17 <assignment> present refused capability-blocked, its own wording and issue
T-18 data association on an event refused capability-blocked (no attach API), its issue
T-19 <ioSpecification> on <process> refused capability-blocked, ADR-011 §2.5 named, its issue
T-20 <property> as an association end refused capability-blocked
T-21 UserTask with an <ioSpecification> real parameters (no WithoutParams); renderer placeholder still present
T-22 UserTask without one today's placeholder pair, unchanged
T-23 <inputSet> written outside an ioSpecification refused with §10.4.1 (FR-6)
T-24 duplicate parameter names in one direction the model's own uniqueness message, file id attached (NFR-4)
T-25 ids of ioSpec/params/sets/associations all join the §4.11 ledger
T-26 end-to-end: the §4a document the engine executes it and the data actually moves
T-27 two <dataInput>s naming one itemSubjectRef refused as one parameter declared twice (§4.3a)
T-28 two untyped inputs adopting from one data element the same refusal (§4.3a)
T-29 <ioSpecification> on a <subProcess> / <callActivity> refused citing the containment rule and its § (§4.7a). The <callActivity> case is superseded — see §4.7a; a call activity declares its parameters now.

§7 Milestones

# Scope
M1 Parse: <ioSpecification> body, the two sets, both association kinds into nodeBody; the ledger claims; FR-6's sections rows
M2 Parameters: build + WithParameters, set flags, the UserTask narrowing (FR-1, FR-2 import half, §4.5)
M3 Associations: resolution incl. rule 2, the §4.3 reconciliation, wiring through Associate* (FR-3, FR-4)
M4 The refusals: standing (multi-set), capability-blocked (transformation/assignment/multi-source, events, process, property), ADR-024 §2.16 rows + issues (FR-5, §4.6–§4.8)
M5 The §4a end-to-end, the wording sweeps, §10

§8 Cross-doc

Outgoing references are SAD-001 v.1.1, ADR-011 v.7, ADR-024 v.4, ADR-030 v.1, ADR-024 §2.16, and the siblings SRD-089.A/.F plus SRD-063 and SRD-068 — all upward or sideways. This stage bumps ADR-038 to v.2 (four §2.3 rows, §4.8), which is part of its own landing, not a separate branch.

§9 Definition of Done

  • FR-1…FR-6 wired and covered by §6.
  • make ci PASS on the committed branch, judged by .ci/last-run.json.
  • Diff-coverage ≥95% (NFR-2), measured after each milestone is committed.
  • No pkg/model change (NFR-1).
  • The ADR-024 §2.16 register carries the new rows with real issue numbers.
  • Every § the code emits is one the extract supports (NFR-5).
  • §10 filled at landing.

§10 Implementation summary

Five milestones, in the order §7 planned them.

M Commit What landed
M1 27b956a7 the parse layer: ioSpecification, the single set per direction, both association kinds; the ledger claims; FR-6's sections rows — deliberately unregistered until the build half existed
M2 276255d5 parameters onto the eight task kinds through WithParameters; the §4.3a duplicate-item refusal; the §4.7a containment refusal; the UserTask narrowing (§4.5)
M3 7210702e the association pass: rule-2 retargeting, the §4.3 adoption, wiring through Associate*; the #328/#329/#331 refusals
M4 52ce2222 the family's last refusals reworded — nothing staged remains — and ADR-024 §2.16's four register rows
M5 0e2a97c0 T-26: the imported data flow runs to completion on a real thresher

NFR-1 held. pkg/model is untouched across the stage — every wire used WithParameters, NewParameter, the two ParameterOptions and the two Associate* families, exactly the doors §1 located.

NFR-2 held. Diff-coverage after the last milestone: 98.4% of 2423 changed lines (min 95). The data-flow files' only uncovered blocks are their errs.Invariant guards and two fallbacks the owners gate makes unreachable.

Two details the plan did not spell out, recorded here:

  1. An empty item's id is derived, not equal. emptyItem names its item <elementID>:item, so an untyped, un-associated parameter carries din1:item rather than din1. Uniqueness — the §4.3a and §4.11 properties — holds either way; the §10.4.1 type-match refusals name the ids as they really are.
  2. A targetRef naming another activity's parameter got its own wording: the id exists and is even the right kind, so a wrong-kind message would send the modeler hunting a typo that is not there.

The sweep inverted, as designed. T-24's staged sweep shrank milestone by milestone and finished as its own opposite: the post-landing wording sweep now asserts that no refusal names SRD-089.G as a plan, and that every survivor names a capability row or the standard's position.

Open questions

None. (The two authoring-time questions — issue numbers, and whether the property end shares #328 — were resolved at review: #328–#331 exist, and the property end is its own row, §4.8.)