8. Layer 5 — Constraints
Contents of DISL 0.4
Constraints state what makes a diagram valid. They are written in CEL, evaluated by the runtime (live, on save, or on demand) and by headless validators, and reported to users with explanations and quick fixes.
{
"constraints": {
"groups": {
"structure": { "label": "Structure", "doc": "Rules without which the machine cannot run." },
"style": { "label": "Style guide", "enabledByDefault": false }
},
"rules": [
{
"id": "singleInitial",
"group": "structure",
"scope": "diagram",
"rule": "diagram.nodesOfType('InitialState').size() == 1",
"severity": "error",
"message": "A state machine needs exactly one initial state.",
"doc": { "rationale": "Execution must start somewhere, and only in one place." },
"fixes": [
{ "label": "Add an initial state", "when": "diagram.nodesOfType('InitialState').size() == 0",
"actions": [ { "create": { "type": "'InitialState'", "at": "[40.0, 40.0]" } } ] }
]
}
]
}
}
| Property | Type | Description |
|---|---|---|
rules |
Constraint[] | Ordered list of constraints. |
groups |
map → {label, doc, enabledByDefault, severityOverride} |
Groups users can enable, disable or re-rate as a unit ("rule sets"). |
defaults |
{severity, timing} |
Defaults for rules. |
blockSaveOn |
"never", "error" |
Whether saving is prevented while errors exist. Default "never" (never lose work); DID definitions are saved with findings and remain loadable. |
builtIn |
map built-in id → BuiltInSetting | Tune the built-in constraints (8.7). |
order |
QualifiedId[] | (DISL 0.3) The codes in the order findings are listed (8.6). Default: none, which keeps the order of 8.6. |
BuiltInSetting ($defs/BuiltInSetting), the value of each constraints.builtIn entry:
| Property | Type | Description |
|---|---|---|
severity |
"error", "warning", "info", "hint" |
Re-rates the built-in. |
enabled |
bool | Switches the built-in off. Default true. |
code |
QualifiedId (hyphens allowed) or {cel} |
The code reported with its findings (8.6), for example "dependencies.duplicate-id". (DISL 0.3) {cel} computes it per finding (below). |
message |
Message | Replaces the runtime's own wording of the built-in's findings (8.6); evaluated in the constraint context plus detail. |
refusal |
Message | Replaces the runtime's own wording when the built-in refuses a gesture (8.4); evaluated in that gesture's context plus violation. Without it a refusal is worded by the runtime, never taken from message. |
oncePerGroup |
"second", "last" |
(DISL 0.3) Reports a group of duplicates once, at its second or its last member (below). Default: none, which flags every member after the first. |
doc |
Doc |
A code per case (DISL 0.3). One built-in can cover breaches a tool reports under several codes, as std.endpoints covers an influence into a trigger, a self-influence and a repeated influence. A code given as {cel} is evaluated per finding, in the constraint context plus detail, and returns a QualifiedId. A computed code whose result is not a QualifiedId is a specification error reported once, and the finding then carries the built-in's id.
"std.endpoints": { "severity": "warning",
"code": { "cel": "self.target.isA('Trigger') ? 'ghg.influence-into-trigger' : (self.source == self.target ? 'ghg.self-influence' : 'ghg.duplicate-influence')" } }
An influence from steam-engine to itself is reported as ghg.self-influence. A relation that breaks several limits is reported once per limit, and detail.violation (8.7) says which, so a computed code can report a loop that is also a repeat under both codes.
One finding per group (DISL 0.3). Two built-ins flag the members of a group of duplicates: std.duplicateId the elements that hold one id, in reading order, and std.endpoints, in its parallel case, the relations of one type that allowParallel: false forbids between the same two elements (in the same direction, or in either direction for an undirected type), in model order (8.7).
- Without
oncePerGroup, each member after the first is flagged, as 8.7 says. - With
oncePerGroup, a group MUST be reported by exactly one finding:"second"at its second member,"last"at its last. Itsdetailis the one that member would have had;countofstd.duplicateIdstill counts every holder. oncePerGrouphas no effect on any other built-in, nor on the other cases ofstd.endpoints.
"std.duplicateId": { "code": "ghg.duplicate-id", "oncePerGroup": "last",
"message": { "cel": "'`' + detail.id + '` is declared ' + string(detail.?count.orValue(2)) + ' times; an id names one entry.'" } }
An id held by three hype cycle entries is reported once, at the third, as "a is declared 3 times; an id names one entry."; three influences from one trend to another in one direction, with oncePerGroup: "second" on std.endpoints, are reported once, at the second.
detail is a map whose keys each built-in lists in 8.7; violation is a map with rule (the built-in id) and the keys 8.4 lists per built-in. detail MUST be bound only in a built-in's message, and violation only in its refusal. Without message or refusal, the runtime words and localises the text itself, as in 0.1.
"builtIn": {
"std.multiplicity": { "refusal": { "cel": "'Refused by the cardinality check: ' + target.name + ' already has its ' + violation.relationType + ' parent, and may have ' + string(violation.max) + '.'" } },
"std.acyclic": { "refusal": { "cel": "'Refused by the cycle check: ' + target.name + ' already owns ' + source.name + ', so this link would make an ownership loop.'" } },
"std.unparseable": { "code": "rdf.unparseable", "message": { "cel": "'This is not RDF that can be read: ' + detail.reason" } }
}
| Property | Type | Description |
|---|---|---|
id |
identifier | Required, unique. Stored with suppressions and reported in findings. |
code |
QualifiedId (hyphens allowed) | The code reported to users and tools with every finding of this rule, for example "databricks.duplicate-task-key". Supersedes the extension property x-adp-ruleId and rule ids kept in label or tags. Suppressions stay keyed by id. |
label |
LocalizedText | Short name ("Single initial state"). |
doc |
Doc | summary explains the rule; rationale why it exists; description how to comply; examples valid and invalid snippets. Runtimes show it with every reported finding. |
kind |
ConstraintKind | Default "invariant" (8.3, 8.4). |
scope |
"diagram", "node", "relation", "port", "*", TypeRef or TypeRef[] |
Which elements the rule is evaluated for; self is bound to each. diagram evaluates once. Default "diagram". |
over |
"model", "view" |
What the rule is evaluated over. model (default, the 0.1 meaning): once per model, however many views or readings of it are open. view: once per view, with view bound. |
forEach |
Expression → list | Items to judge one by one: evaluated once for each scope element, and when, rule, severity, message, target, location, subject and attribute are then evaluated per item with item and index bound. One finding per failing item. |
when |
Expression | Precondition; the rule is only evaluated where it holds. |
rule |
Expression → bool | The condition that MUST hold. Required unless forEach is present; with forEach the default is false, so every item yielded is a finding. |
severity |
"error", "warning", "info", "hint", or {cel} |
Default "error". |
message |
Message | Finding text. CEL messages may interpolate values: { "cel": "'State ' + self.name + ' is unreachable'" }. |
target |
"self" or Expression |
Element(s) the finding is attached to; an expression returning an element or list (for example all duplicates). Default self; with forEach, item when the item is an element, else self. |
location |
Expression → SourceLocation map or null |
Where in the files the finding is (8.6), when that is not the target's own location or is more precise. Default: the target's location() (12.2). |
subject |
Expression → string | A display string naming what the finding is about when that is not drawn: a triple, a key path, a prefix (8.6). |
attribute |
path | Attribute to mark in forms. |
timing |
("live", "save", "explicit", "export")[] |
When evaluated. live: after each transaction (debounced); save: before saving; explicit: on "Validate" command; export: before export. Default ["live", "save"]. |
enforcement |
"report", "prevent", "prevent-and-report" |
prevent: a transaction that would make the rule false (for elements it was true for before) is rejected with the message. Default "report" for invariants, "prevent" for gesture kinds. |
fixes |
QuickFix[] | Suggested corrections. |
suppressible |
bool | Users may suppress the finding for a specific element, or for its subject when it has no element; suppressions are stored in the DID definition (11.7). Default true for warnings and below, false for errors. |
enabled |
bool | Default true (subject to group). |
group |
group id | |
tags |
string[] | |
cost |
"cheap", "expensive" |
Hint: expensive rules (graph traversals) MAY be evaluated with lower frequency or off the main thread. |
One finding per item. A constraint with forEach MUST produce one finding for each item for which when holds and rule does not, in list order, and none when the list is empty. A constraint with forEach MUST have enforcement: "report"; a validator MUST reject any other enforcement, so prevent stays defined over elements only. With positionIn (12.4) and the graph functions diagram.cycles and diagram.knots (12.4), forEach reports one finding per cycle, naming its members in loop order, and flags only the second and later of a group of duplicates:
{ "id": "unlabelledLoop", "code": "causal-loop.unlabelled-loop", "scope": "diagram", "severity": "info", "cost": "expensive",
"forEach": "diagram.cycles('CausalLink', 500).filter(c, !diagram.nodesOfType('Loop').exists(l, signature(l.members) == signature(c)))",
"target": "item",
"message": { "cel": "'The links form a feedback loop through ' + item.map(v, v.name).join(' → ') + ' that no loop statement names.'" } }
{ "id": "duplicateTaskKey", "code": "databricks.duplicate-task-key", "scope": "Task", "severity": "error",
"rule": "self.positionIn(diagram.nodesOfType('Task').filter(t, t.key == self.key)) == 0",
"message": { "cel": "'The task key \\'' + self.key + '\\' is used more than once. Every task needs its own.'" } }
Model or view. diagram MUST always denote the whole model. A constraint with over: "model" MUST be evaluated once per model, however many views or readings of it are open, and its findings MUST be shared by all of them. A constraint with over: "view" MUST be evaluated once per view, with view bound to that view (members id, viewpoint, name and members → list(Element)), and each of its findings MUST carry that view's id. A rule about the views of the model as a whole reads diagram.views (12.2):
{ "id": "elementNotOnAnyView", "code": "c4.element-not-on-any-view", "over": "model",
"scope": ["Person", "SoftwareSystem", "Container", "Component"],
"when": "diagram.views.size() > 0",
"rule": "diagram.views.exists(v, v.members.exists(m, m.id == self.id))",
"severity": "warning",
"message": { "cel": "\"'\" + self.name + \"' is on no view.\"" } }
Location and subject. When a finding targets an element and the constraint declares no location, the runtime MUST use the target's location() when it has one. location returns a SourceLocation map (8.6) or null; subject returns a string that is shown, never resolved as an id. A rule about something the model does not hold as an element locates it itself:
{ "id": "unusedPrefix", "code": "sparql.unused-prefix", "scope": "diagram", "severity": "info",
"forEach": "diagram.prefixes.filter(p, !p.used)",
"location": "{'file': diagram.file, 'line': item.line}",
"subject": "'PREFIX ' + item.prefix + ':'",
"message": { "cel": "\"The prefix '\" + item.prefix + \":' is declared but never used.\"" } }
Invariants (kind: "invariant") are statements about the current state of the diagram, evaluated for each element in scope:
[
{ "id": "nameRequired", "scope": "State", "rule": "self.name.trim() != ''",
"attribute": "name", "severity": "error", "message": "Every state needs a name." },
{ "id": "uniqueNames", "scope": "State",
"rule": "diagram.nodesOfType('State').filter(s, s.name == self.name).size() == 1",
"severity": "warning", "message": { "cel": "'The name \"' + self.name + '\" is used more than once.'" },
"target": "diagram.nodesOfType('State').filter(s, s.name == self.name)" },
{ "id": "reachable", "scope": "State",
"when": "!self.isA('InitialState')",
"rule": "diagram.nodesOfType('InitialState').exists(i, self in i.reachable('Transition'))",
"severity": "warning", "cost": "expensive",
"message": { "cel": "'State \"' + self.name + '\" can never be reached.'" } },
{ "id": "deterministic", "scope": "State",
"rule": "self.outgoingOf('Transition').map(t, t.trigger).isUnique()",
"severity": "error",
"message": "Two transitions leave this state on the same trigger.",
"doc": { "rationale": "The machine would not know which transition to take." } }
]
Gesture constraints are evaluated before a user action is applied, while the user is still dragging, so the runtime can show a "not allowed" cursor, apply the dropReject state and explain why. Their default enforcement is prevent.
kind |
Evaluated when | Variables (in addition to diagram, env) |
|---|---|---|
connect |
Creating or reconnecting an edge | relationType (string), source, target (elements or ports), sourcePort, targetPort, self (the edge when reconnecting, else null) |
containment |
Dropping or creating an element inside a container | child (element or {type} for new elements), parent, slot |
create |
Creating an element with a tool or operation | elementType (string), parent, position, dropTarget (the topmost element under the pointer, which may differ from parent when it is not a container; null on empty canvas), tool (the id of the tool used, or null) |
delete |
Deleting elements | self, selection (all elements being deleted) |
placement |
Moving or resizing | self, oldBounds, newBounds (in domain values: newBounds.x is a timestamp on time axes), newParent, gesture ("move", "resize" or "reparent") |
change |
Changing an attribute in a form or label | self, attribute, oldValue, newValue |
reorder |
Changing an element's place among its siblings (moveUp, moveDown, a reorder action, or dragging a child along an ordered stack or flow) |
self, parent, oldIndex, newIndex, siblings (the siblings in their current order) |
[
{ "id": "noEntryIntoInitial", "kind": "connect",
"when": "relationType == 'Transition'",
"rule": "!target.isA('InitialState')",
"message": "Transitions cannot enter the initial state." },
{ "id": "noOverlapPerResource", "kind": "placement", "scope": "Task",
"rule": "!diagram.nodesOfType('Task').exists(t, t.id != self.id && t.assignee == self.assignee && t.start < newBounds.x2 && newBounds.x < t.end)",
"enforcement": "report", "severity": "warning",
"message": "This resource already has a task in that period." },
{ "id": "lockedPhase", "kind": "delete", "scope": "Phase",
"rule": "!self.locked", "message": "Unlock the phase before deleting it." }
]
When enforcement is report on a gesture kind, the gesture is allowed and a finding is reported afterwards if the corresponding state still violates the rule.
Reorder. A reorder MUST change only the order among siblings, as one transaction, and is refused by reorder constraints with their message. Dragging a child along the direction of a container whose layout is stack or flow (6.9) is a reorder gesture.
{ "id": "templateStepsKeepOrder", "kind": "reorder", "scope": "Step",
"rule": "!siblings.exists(s, s.fromTemplate)",
"message": "Some of this job's steps come from a template, so their order cannot be changed here." }
A gesture constraint with enforcement prevent MUST refuse the gesture before it is applied and MUST show its message, evaluated in the gesture's context, as the refusal (2.3).
Order of gesture checks. When several checks would refuse one gesture, a runtime MUST show the refusal of the first one in this order, and MAY list the others on request:
- the notation's refusal for a gesture it switches off for the element's kind (
refusals, 6.9, 6.10), then the refusal of a gesture on a derived element thateditsdoes not map (4.11.4), then that of a gesture that would store an ephemeral id (11.5.3); - the diagram-wide edit gate (
behavior.editGate, 9.1), then the edits withheld by an exceeded budget (3.2.1), for a gesture that changes the model; - the built-in constraints, in the order
std.endpoints,std.containment,std.multiplicity,std.acyclic,std.axisBounds,std.facets(8.7); - the declared gesture constraints, in the order of
rules.
A refused gesture changes nothing. A refusal about a selection of several elements MUST be evaluated once for the gesture, with selection bound to all of them and self bound to the element under the pointer (or the first of the selection).
A built-in's refusal (8.1) sees the gesture's variables plus violation, a map with rule (the built-in id) and:
| Built-in | Keys of violation |
|---|---|
std.endpoints |
relationType, end ("source" or "target") |
std.containment |
parentType, childType, slot |
std.multiplicity |
min, max, count, relationType or type, end |
std.acyclic |
relationType, cycle (list of elements, in loop order) |
std.axisBounds |
axis, min, max |
std.facets |
attribute, facet, limit |
| Property | Type | Description |
|---|---|---|
label |
Message | Menu text ("Rename to 'Idle 2'"), in the constraint context. |
doc |
Doc | What the fix does. |
when |
Expression | Offer the fix only if true. |
actions |
Action[] | Behavior actions (9.4) executed as one transaction with self bound to the finding's element. |
operation |
operation id | Alternatively run a declared operation. |
preferred |
bool | The fix offered by "fix all" and keyboard shortcut. |
applyToAll |
bool | May be applied to all findings of this constraint at once. |
Evaluating constraints, and reading a model, produce findings: each is the result of one rule or of the reader, with a severity, a message and where it applies. DISL 0.1 called them problems; many findings are info or hint, so 0.2 uses the wider word. A host MAY keep its platform's own name for the panel that lists them.
The finding. A finding carries:
| Property | Type | Description |
|---|---|---|
constraint |
constraint id or built-in id | The rule that raised it. Required. Suppressions are keyed by it. |
code |
QualifiedId | The rule's code (8.1, 8.2). When the rule has one, the finding MUST report it. |
severity |
"error", "warning", "info", "hint" |
Required. |
message |
string | The rule's Message, evaluated. Required. |
target |
element or elements | What the finding is attached to; the diagram itself for a rule with scope: "diagram" and no other target. |
attribute |
path | The attribute to mark in forms. |
location |
SourceLocation | Where in the files the finding is (below). |
subject |
string | A display string naming something not drawn: a triple, a key path such as resources.jobs.x, a prefix. It is not an id, and runtimes MUST NOT resolve it as one. |
view |
view id | The view it was found in, for a rule with over: "view" (8.2). |
fixes |
QuickFix[] | The rule's quick fixes whose when holds (8.5). |
Every finding MUST carry a rule, a severity and a message, and at least one of target, location and subject; it MAY carry several. Runtimes MUST present findings on the canvas (the invalid/warning states and a badge), in a list of findings (sortable, filterable, where choosing one reveals its element or goes to its location), and in forms (next to attribute). A runtime MUST present a finding with a location so that the user can go to that place in the file, and a finding with a subject with the subject shown, and MUST NOT fail to present a finding because it has no element.
SourceLocation ($defs/SourceLocation) says where in a file something is:
| Property | Type | Description |
|---|---|---|
file |
string | The file, as a relative path with / separators, relative to the diagram's subject (below). Required. |
line |
integer ≥ 1 | The 1-based line. Absent: the finding is about the whole file. |
column |
integer ≥ 1 | The 1-based column, counted in Unicode code points. Requires line. |
length |
integer ≥ 0 | The length of the span, counted in Unicode code points. Requires column. |
line and column MUST be 1-based, and column and length MUST count Unicode code points, not UTF-16 code units or bytes. file MAY name a file other than the model's own (an included file, a sibling in a folder, a lock file). A host MAY rebase file for display, for example relative to its project, but MUST NOT write a rebased path into a finding it passes on.
Subject, primary file and reading order. These three are defined by FBL, the Format Binding Language, which reads foreign files into a model; DISL uses them as FBL defines them and defines none of its own:
- The subject of a diagram is the file or folder it shows. For a diagram stored as a DID definition, the subject is the DID definition file. For a file subject,
fileis that file's own name; for a folder subject, it is the path of a file inside the folder. - The primary file is the subject when the subject is a file. A folder subject has no primary file.
- Reading order is the order in which the reader produced the elements: record order for a DID definition; for a model read through FBL, document order within a file and, for a folder, its files in ordinal order of their relative paths, each in document order.
positionIn(12.4), repeat counters in derived ids (11.5) and "the second and later duplicates" (8.7) rely on it.
Parse failure. A reader that cannot parse a file of the model MUST report exactly one std.unparseable finding for that file, located at the file, and at the parser's line and column when it has them. A runtime MUST NOT report any other finding whose location names a file for which std.unparseable was reported. When that file is the primary file, the runtime MUST NOT evaluate any constraint over the model. When the subject is a folder, std.unparseable on one of its files never stops rule evaluation: the model read from the remaining files is validated as usual. A runtime MUST NOT crash, hide the diagram, or report a parse failure in any other way than this finding; whether the diagram then opens empty or read-only is for the reader to say (FBL, or DID section 8.1).
Order of findings. Findings MUST be ordered as follows:
- findings raised by the reader (
std.unparseable,std.unreadableEntry,std.missingId,std.duplicateId), in reading order; - the other built-in constraints, in the order of the table in 8.7;
- declared constraints, in the order of
rules; - within one constraint, by scope element in model order (12.5), then by
forEachitem order.
Declared order (DISL 0.3). When constraints.order lists codes, findings MUST instead be ordered as follows, so that a tool can keep the order its users know:
- by the position of the finding's code in
order, a finding without a code taking its constraint's id; findings whose code is not listed come after every listed one; - within one position, the findings the reader raised while reading a file (
std.unparseable,std.unreadableEntry) first, in reading order; - then the others by the line of their element: for a declared rule, the scope element it was evaluated for, or for a rule scoped to the diagram its
forEachitem, else its first target; for a built-in, its element. A finding about a group of duplicates (8.1, 8.7) takes the line of the group's first member, whichever member it is reported at. A finding without a line comes after those with one; - ties keep the order of the list above.
An empty order is the same as none. A hype cycle lists every duplicated influence first, by the line of the first influence of each pair, then every self-influence, and so on in the order of its twelve codes.
The headless validator's output and a runtime's default presentation MUST use this order; a runtime MAY let the user sort differently.
Suppressions. A user may suppress a finding of a suppressible rule for an element, or, for a finding without an element, for its subject; suppressions are stored in the DID definition (DID, section 3) and are keyed by the rule's id, never by its code. A suppression MUST NOT name an ephemeral id (11.5).
Headless validator output. A headless validator MUST write its findings as a JSON array valid against $defs/ValidatorOutput, in the order above, and MUST exit with a non-zero status when any finding has severity error. A stored copy of that output (such as findings.json beside this document) MAY use the object form { "$schema", "findings" } of $defs/ValidatorOutput, whose findings is the same array. Each item keeps the 0.1 keys constraintId (the finding's constraint), severity, message, elementId, attribute and pointer, and may add code, elementIds, location, subject and view. elementId MUST equal the first of elementIds when both are present. pointer (a JSON Pointer) MUST be present for a finding about a record of a DID definition, and MUST be absent when location names a file that is not a DID definition. A headless validator MUST be given, or MUST derive from its input, the subject that location.file and the file-system facts (12.4) are relative to.
[
{ "constraintId": "std.duplicateId", "code": "dependencies.duplicate-id", "severity": "warning",
"message": "The id 'api-gateway' is declared more than once, which makes every reference to it ambiguous.",
"elementIds": ["api-gateway"], "location": { "file": "shop.dgr", "line": 14 } },
{ "constraintId": "std.unparseable", "code": "sparql.unparseable", "severity": "error",
"message": "This query does not parse: a closing brace is missing.",
"location": { "file": "q.rq", "line": 3, "column": 17 } },
{ "constraintId": "nonConceptTarget", "code": "skos.nonconcept-target", "severity": "warning",
"message": "ex:Widget is the object of skos:broader but is not a concept.",
"subject": "ex:Widget", "location": { "file": "stw.ttl", "line": 812 } }
]
Metamodel declarations generate built-in constraints automatically, and the reader and the runtime raise the others in the situations below. They behave like declared constraints, can be re-rated, switched off, given a code, a message for their findings and a refusal for the gestures they refuse in constraints.builtIn (8.1); without them, their texts are worded and localised by the runtime.
| Id | Generated from | Default severity / enforcement |
|---|---|---|
std.required |
required: true |
error / report |
std.facets |
min, max, pattern, minLength, … |
error / report (and prevent in forms) |
std.unique |
unique |
error / report |
std.multiplicity |
multiplicity, children.min/max, perType, relation end min/max, port multiplicity |
lower bounds: warning / report; upper bounds: error / prevent |
std.endpoints |
relation source/target, ports, accepts, allowSelfLoops, allowParallel; silent on a missing target of an optional target end (4.9) |
error / prevent |
std.containment |
children.allowed, slots |
error / prevent |
std.acyclic |
acyclic: true, over the type and all its subtypes (4.9) |
error / prevent |
std.references |
references to missing elements | error / report |
std.axisBounds |
axis min/max, system bounds |
error / prevent |
std.typeExists |
unknown types in loaded DID definitions | error / report (element preserved, 14.3) |
std.unparseable |
the reader, once per file of the model that cannot be parsed; replaces every other finding located in that file (8.6) | error / report |
std.unreadableEntry |
the reader, per entry of a parsed file that cannot become an element (an unknown key, a malformed value, a line in no known form); the entry is kept and not drawn | warning / report |
std.missingId |
the reader or runtime, per element without a usable id, or whose derived id cannot be computed (11.5) | warning / report |
std.duplicateId |
the reader or runtime, per element whose id, compared as persistence.ids.compare says, equals that of an element earlier in reading order; only the second and later are flagged (11.5), or the one oncePerGroup names (8.1) |
warning / report |
std.mixedPrecision |
the runtime, when attributes of one element tied by samePrecisionAs (4.3) hold values written with different precisions (precisionOf, 12.4) |
warning / report (and prevent in forms) |
std.ephemeralViewData |
the runtime, per stored view data, style override or suppression keyed by an ephemeral id (11.5) | info / report |
std.pluginMissing |
the runtime, once per declared plugin that is absent and whose CEL functions are called (13.1) | warning / report |
std.derivedFailed |
the runtime, per derived type whose from fails, and per derived type and error message for items whose other expressions fail (4.11.5) |
warning / report |
std.derivedId |
the runtime, when a derived id equals another derived or a stored id; the later one in computation order is not drawn (4.11.5) | warning / report |
std.derivedEnds |
the runtime, once per derived relation type some of whose items have ends that do not satisfy its source or target (4.11.3) |
warning / report |
Each built-in binds detail in its message (8.1) with the keys below; the built-ins of 0.1 bind the keys that name what failed (attribute, facet, limit, min, max, count, relationType, missingId).
| Built-in | Keys of detail |
Location and target |
|---|---|---|
std.unparseable |
reason (the parser's message) |
the file, at the parser's line and column when known; no element |
std.unreadableEntry |
reason, entry (a map: text, the entry's source text as written; path, a JSON Pointer (RFC 6901) to the entry in the file's parsed tree, or '' when the format has no tree) |
the entry's location; subject names the entry |
std.missingId |
reason ("absent", "empty", "pattern" or "derived") |
the element, with its new or ephemeral id, and its location |
std.endpoints |
relationType (0.1), violation (DISL 0.3: "source" or "target" for an end whose element the relation type does not allow there, "selfLoop" for a loop allowSelfLoops: false forbids, "parallel" for a repeat allowParallel: false forbids) |
the relation, and its location; for parallel, each second and later relation of the group, or the one oncePerGroup names (8.1) |
std.references |
missingId (0.1), end (DISL 0.3: "source", "target" or the name of the reference attribute that names nothing) |
the element holding the reference, and its location |
std.duplicateId |
id, first (the element that keeps the id), count (DISL 0.3: the number of holders of the id, the first included) |
each second and later element, and its location |
std.mixedPrecision |
attribute, other, precision, otherPrecision |
the element and the attribute |
std.ephemeralViewData |
key (the stored id) |
the element when present; otherwise subject is the stored key |
std.pluginMissing |
plugin (the plugin id), functions (the names of its functions that were called) |
the diagram; no location |
std.derivedFailed |
type, reason (the evaluation error), property (the failing property, such as from or id), count (the items dropped) |
the diagram; subject names the type |
std.derivedId |
id, first (the element that keeps the id), type |
the element that is not drawn is named by subject; the finding targets first |
std.derivedEnds |
type, end ("source" or "target"), count (the items dropped) |
the diagram; subject names the type |
The keys marked DISL 0.3 are bound by a 0.3 runtime; a message that must also read under 0.2 reads them optionally, as detail.?count.orValue(2), detail.?end.orValue('target') and detail.?violation.orValue('parallel'), so a duplicate id is worded "a is declared 3 times; an id names one entry." where the count is known.
std.endpoints raises one finding per limit a stored relation with both ends breaks, in the order source, target, selfLoop, parallel, each with its own violation. A hype cycle reads it in its computed code (8.1), falling back to the relation itself under 0.2:
"code": { "cel": "cel.bind(v, detail.?violation.orValue(self.target.isA('Trigger') ? 'target' : (self.source == self.target ? 'selfLoop' : 'parallel')), v == 'target' ? 'ghg.influence-into-trigger' : (v == 'selfLoop' ? 'ghg.self-influence' : 'ghg.duplicate-influence'))" }
An influence from a trend to itself that repeats an earlier one is reported twice, as ghg.self-influence and as ghg.duplicate-influence.
A reader MUST report std.unparseable, std.unreadableEntry, std.missingId and std.duplicateId as they occur, and MUST NOT fail to open a model because of any of them. Each built-in is raised only from a situation that 0.1 rejected or from a declaration that 0.2 adds, so a valid 0.1 diagram reports nothing new. std.pluginMissing defaults to warning because an absent plugin degrades the diagram without making it unusable (15.2); a specification that cannot work without the plugin declares it required (13.1) instead.
A finding on a derived element (4.11) is located, where the constraint declares no location, at the first of its sources that has a location; one about a model element that is not drawn (filtered out, cut by a budget, excluded by the viewpoint) is still reported and still names it.
Illustrated by
- C4 container diagram: 11 items in this layer
- Causal loop diagram: 6 items in this layer
- Databricks job: 7 items in this layer
- Entity–relationship diagram: 3 items in this layer
- Functional decomposition graph: 8 items in this layer
- Gartner hype cycle graph: 19 items in this layer
- Mind map: 8 items in this layer
- RDF graph: 12 items in this layer
- State machine: 9 items in this layer
- Project timeline: 4 items in this layer
Referenced from
- 2. Foundations
- 3. Specification structure, layers and viewpoints
- 4. Layer 1 — Metamodel
- 5. Layer 2 — Coordinate systems, placement and snapping
- 6. Layer 3 — Notation (visual definition)
- 7. Layer 4 — Toolbox, context tools and forms
- 9. Layer 6 — Behavior
- 11. Layer 8 — Persistence
- 12. The CEL environment
- 13. Extensions and plugins
- 14. Processing model
- 15. Conformance
- 17. Complete examples
- Changes from 0.3
- 18. Deprecated aliases
- Appendix A — JSON Schema
- Appendix B — Built-in catalogues
- Appendix C — Glossary
- Appendix D — Design rationale and open questions