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 input — WithParameters 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:
-
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.
-
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
ItemDefinitionto violate, and the adoption is exactly what "takes its type from" means. -
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 yet — buildDataElements 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 itemRef → asm.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: optionalInputRefs →
Optional(), whileExecutingInputRefs → WhileExecuting() (§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), butAssociate*exists only onDataObjectandDataStoreReference.
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 inputinover itemidOrder, optional; one outputoutwhose parameter adoptedidOrder—dout1names noitemSubjectRefand its association's other end isdo1(§4.3 case 2).- Two associations on
t1: the input one fromdo1(scope-routed, SRD-063 FR-5), the output one todo1— both wired through the data object's ownAssociate*, matching onidOrder. res.Droppedis empty: everything in this file maps.- The process runs: the engine's
LoadDatafinds the input by item id, the scope servesorder, andUploadDatawrites 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 ciPASS on the committed branch, judged by.ci/last-run.json.- Diff-coverage ≥95% (NFR-2), measured after each milestone is committed.
- No
pkg/modelchange (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:
- An empty item's id is derived, not equal.
emptyItemnames its item<elementID>:item, so an untyped, un-associated parameter carriesdin1:itemrather thandin1. 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. - A
targetRefnaming 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.)