The Transitrix Studio validator provides 3 layers of validation:
| Layer | Mechanism | Coverage | Implementation |
|---|---|---|---|
| L1 | AJV schema validation | YAML type safety, required fields | src/parser.ts |
| L2 | IR structural validation | Pool/lanes exist, element counts, IDs unique | src/validator.ts (RD-096) |
| L3 | Semantic BPMN rules | Start event exists, event types valid, gateway constraints | RD-097 onward |
Validation is non-blocking: findings are surfaced in the API, CLI, and web UI, but do not prevent compilation.
Validation runs on one execution axis — scope (methodology ADR
2026-06-11-validation-two-axis-model.md; Studio referencing ADR
decisions/2026-06-11-validation-runtime-convergence.md):
| Scope | Command | Coverage |
|---|---|---|
| file (default) | transitrix validate <input.yaml> |
A single notation file: the BPMN structural/semantic rules documented below. |
| repo | transitrix validate --scope=repo [--root <dir>] |
The whole loaded canon/ model: referential integrity, atomicity, id uniqueness, policy. |
--scope=repo ports the whole-repo checks previously owned by the methodology's
Python .validators/lint.py onto the shared @transitrix/diagrams model
(packages/diagrams/src/repo-validate/), so there is a single validation
runtime. It scans <root>/canon/elements/** (elements) and
<root>/canon/relations/** (relations), then reports findings shaped
{ scope, id, message }. Any finding exits non-zero — the CI gate.
Repo-scope checks (parity reference: the acme_corp worked example, which
passes with zero error-severity findings from the checks below — it does
carry GOALS-011/FGCA-013 warnings from the strategy-chain rules
described below, plus pre-existing findings from the separate compliance
suite, see below):
- YAML syntax — an unparseable canon file is reported and graph checks are skipped.
- ID uniqueness — the same
iddefined in more than one file. - Atomicity — an element file carrying an inline
relations:section (relations belong incanon/relations/). - Referential integrity — a relation endpoint (
from/to, or the legacysource/target) that does not resolve to a known element. - Policy — an element marked
Active/Production(metadata.status) with nometadata.owner. - ArchiMate layer-semantics — endpoint-type constraints for the relation
kinds with a formally-defined methodology semantics (
unit_located_at,located_at,offers,realizes,hosts,uses,process_parent), plusTSVC-003(TECHNOLOGY_SERVICE.node → NODE) andINT-002(INTEGRATION interface endpoints → APPLICATION).process_parentalso firesREL-007(self-reference, error) andREL-008(cycle in the child→parent graph, warning). lint.py ships this phase as a no-op stub; Studio is ahead of the Python tool here — these findings are TypeScript-only. - Strategy-chain semantics (
GOALS-009..011,ACTION-007..011,FGCA-008..014) — see below. - Standalone-element envelope hygiene (
GOAL-ELEM-002/003,ACTION-001/002/005) — see below. - Doc-level grammar (
GOALS-001..007,ACT-001..003/016/021,FGCA-001..007/015) — each notation's own per-file validator, surfaced under theviewsarray (notcanon) when the document lives undercanon/views/**— see below.
Repository validation enumerates files at every depth in canon/, field/,
and codex/, including zone roots, hidden directories, and non-YAML files.
Each file is validated, explicitly reported as unvalidated, or narrowly exempted.
Nested catalogues with their own transitrix.yaml remain independent catalogues.
Only a regular file named exactly .gitkeep containing zero bytes is placeholder
metadata. It contributes no model records, references, or coverage counts, and
may remain beside model content. A newline, space, BOM, comment, or other content
removes the exemption; a symbolic link never qualifies.
Unsupported files without admission metadata receive ZONE-001 (an error in
canon/field, a warning in codex). Unsupported files with admission metadata,
including YAML front matter, receive ZONE-003 errors. Empty model YAML receives
ZONE-002; malformed YAML retains the error-severity YAML diagnostic at any
depth, including hidden files and zone roots.
Only codex/sources/ is an archival boundary. Its files are excluded from model
validation, but admission records there produce ADMIT-012 errors.
canon/sources/, field/sources/, and codex/internal/sources/ receive ordinary
zone checks. JSON output records exempt paths and reasons in coverage.excluded.
Ported from DSM's Go Validate* functions (api02/internal/importer/ {goals,activities,fgca}.go in transitrix-dsm) onto the standalone-element
shape (canon/elements/01_motivation/goals/, canon/elements/ 05_implementation/{actions,changes}/, canon/elements/01_motivation/ factors/), so DSM can shell out to this CLI instead of maintaining its own
copy of these checks. Each finding carries the DSM rule code as
RepoFinding.ruleId, so DSM can map a CLI finding back onto its existing
import-log taxonomy, plus a severity ('error' | 'warning') matching
DSM's own Issue.Severity classification for that rule.
| Rule | Severity | Checks |
|---|---|---|
GOALS-009 |
warning | A GOAL's parent is set but does not resolve to a known GOAL (orphan). |
GOALS-010 |
error | A GOAL element's parent chain contains a cycle. |
GOALS-011 |
warning | A GOAL has no parent and level >= 1 (backlog — untethered from the tree until attached). |
ACTION-007 |
warning | An ACTION's predecessors entry, or its parent, does not resolve to a known ACTION (orphan). |
ACTION-008 |
error | An ACTION element's predecessors graph contains a cycle. |
ACTION-009 |
error | An ACTION element lists itself in its own predecessors. |
ACTION-010 |
error | An ACTION element's start_date/end_date is not a valid YYYY-MM-DD date, or end_date is before start_date (equal is allowed — e.g. a milestone). |
ACTION-011 |
error | An ACTION element's duration (or the duration_days alias), labor_cost, resources_cost, effort, or score is negative under a methodology 7.0.0-or-later manifest. |
FGCA-008 |
error | A GOAL's factors references a DRIVER id that does not resolve to a DRIVER element. |
FGCA-009 |
error | A CHANGE's goals references a GOAL id that does not resolve to a GOAL element. |
FGCA-010 |
error | An ACTION's delivers_changes references a CHANGE id that does not resolve to a CHANGE element. |
FGCA-011 |
error | An ACTION's goals references a GOAL id that does not resolve to a GOAL element. |
FGCA-012 |
warning | A DRIVER is not referenced by any GOAL's factors (unreferenced). |
FGCA-013 |
warning | A GOAL is not referenced by any CHANGE's or ACTION's goals (unreferenced). |
FGCA-014 |
warning | A CHANGE is not referenced by any ACTION's delivers_changes (unreferenced). |
GOALS-008 is the one DSM rule still not ported, at either severity. Both
of its cases ("type not declared in goal_types" and "level doesn't match
the type's declared level") need the goal_types[] catalogue, which lives on
the goals-tree view (canon/views/goals/**, notations/views/04-goals.md
§5.2) — a zone this validator's RepoModelInput does not load (only
canon/elements/** and canon/relations/**). There is no standalone-element
data this rule could run against without the validator growing a third input
zone. Flagged for a decision: skip permanently, or scope a follow-up that
loads the goals-tree catalogue into the repo-scope model.
Why the warning tier exists at all. RepoFinding grew an optional
severity field ('error' | 'warning', defaulting to 'error' when
omitted) precisely so DSM's warn-severity rules could be ported without
becoming blocking findings — every finding that predates this field (the
structural checks above, plus the error-tier strategy-chain rules and
TSVC-003/INT-002) has no severity set and stays implicitly blocking;
validate --scope=repo only exits non-zero on an error-severity finding.
Expect warning-tier noise on parent-light repos, by design. GOAL's
parent field is v0.x-transitional (ELEMENT_PRIMITIVES.md §7.2 — its
canonical home is a goal_parent REL file or the goals-tree view's inline
parent, not the element itself), so most standalone GOAL elements
legitimately omit it. organizations/acme_corp's own GOAL-CUST-1/
GOAL-OPS-1/GOAL-EU-1 are exactly this shape (level 1, no parent on the
element) and do surface GOALS-011 warnings. That is accepted, not a defect:
warnings are advisory and non-blocking, unlike the error tier this repo held
the line on when these rules were first scoped. Similarly, FGCA-012..014
read only the inline factors/goals/delivers_changes arrays (the same
fields FGCA-008..011 already read) — a repo wiring the strategy chain
through REL files instead (e.g. action_goal, per elements/24-action.md
§3) will see FGCA-013/014 warnings on elements that are, in fact, wired
via a relation this validator doesn't yet cross-reference. This is a known
gap shared with the pre-existing error-tier rules, not a regression
introduced by the warning tier.
Ported from DSM's Go Validate*Element functions
(api02/internal/importer/{goal_element,action_element}.go), sibling to the
strategy-chain rules above but covering the per-element envelope instead of
cross-element references — packages/diagrams/src/repo-validate/ check-element-hygiene.ts.
| Rule | Severity | Checks |
|---|---|---|
GOAL-ELEM-002 |
error | GOAL element id is missing. |
GOAL-ELEM-002 |
warning | GOAL element id does not match GOAL-[<middle>-]<INTEGER> (non-fatal — DSM still imports it). |
GOAL-ELEM-003 |
error | GOAL element name is missing. |
ACTION-001 |
error | ACTION element id or name is missing. |
ACTION-001 |
warning | ACTION element id does not match ACTION-[<middle>-]<INTEGER>. |
ACTION-002 |
error | ACTION element type is set but not one of Initiative | Strategic Initiative | Programme | Project | Task. |
ACTION-005 |
warning | ACTION element uses a deprecated alias: notation: activity, an ACTIVITY- id prefix, or the activity_type field (migrate to type). |
GOAL-ELEM-001/ACTION-001's "wrong notation" case is not ported. DSM
identifies a GOAL/ACTION element by its folder location during its import
walk, so a wrong notation field on an already-located file is still
checkable there. This repo-scope model has no such pre-typing —
RepoModelInput.elements is a flat, untyped list — so candidate selection is
content-based (notation === 'goal'/'action'/'activity'), the same
convention check-strategy-chain.ts already uses. An id-prefix-based
candidate signal was tried as an alternative (flag any GOAL-/ACTION-
prefixed id regardless of notation) but produced false positives against
this repo's own test suite, which reuses a GOAL--prefixed id under a
goals/ path for an unrelated notation as a referential-integrity test
double. Flagged for a decision, same as GOALS-008: skip permanently, or
scope a follow-up once the repo-scope model gains path-aware typing.
Not ported: GOALS-007/ACT-004's duplicate-id half. Already covered
generically by checkIdUniqueness above, which spans every canon
element/relation id, not just GOAL/ACTION.
DSM's remaining Validate* doc-level grammar checks — id/name presence,
required-field shape, per-layer id uniqueness, cross-reference grammar — need
no separate port: they are already the per-notation --scope=file validators
this CLI ships (packages/diagrams/src/goals/parse-canonical.ts,
packages/diagrams/src/activities/validate.ts,
packages/diagrams/src/fgca/parse-canonical.ts), using the same DSM rule
codes already (each file's own header comment names its code range). --scope =repo runs every one of them again, once per canon/views/** document, via
runViewValidate (src/repo-validate.ts) — including projection-form
documents (view_config present, no inline array), which are resolved
against canon/elements/** first, the same way each notation's VS Code
preview resolves them.
That sweep's findings land in the JSON output's views array, not
canon — a ViewFinding ({ file, notation, ruleId, severity, message }),
distinct from RepoFinding's cross-reference shape. --scope=repo still
exits non-zero on any error-severity finding regardless of which array it is
in.
| Rule range | Notation | Checks |
|---|---|---|
GOALS-001..006 |
goals |
Document shape, goal_types[]/goals[] entry grammar. |
GOALS-007 |
goals |
Duplicate goals[].id within one document (distinct from the cross-document checkIdUniqueness above). |
ACT-001..003 |
action |
Document/notation shape, activity entry id/name presence. |
ACT-016 |
action |
A milestone (duration 0) with both start_date/end_date pinned must have them equal. |
ACT-021 |
action |
view_config.scope.root_action is set and resolves, and an ACTION the view would otherwise include is not that root and not reachable from it via parent. Warning; the ACTION is omitted from the render. Distinct from ACT-004 (duplicate id in this validator). |
FGCA-001..007 |
dgca |
Document shape, per-layer entry grammar, cross-reference array grammar. |
FGCA-015 |
dgca |
factors[].references_constraint is an array of valid ids. |
A document source is a .ttrs file: prose with {{ … }} directives rather
than a YAML mapping, so it carries no notation: field and never reaches the
per-notation dispatch above. The rules it still shares with every other notation
are the file-level ones — one notation has exactly one extension, and that
notation's files live in that notation's folder (CONTRACT.md §3, rule
HDR-003). runDocumentSourceValidate (src/validate-document-source.ts,
walked from src/repo-validate.ts) checks those from the path and the YAML front
matter alone, with no template parse. Its findings land in the same views
array, carrying notation: "documents" — the view class, since a document source
has no notation value of its own.
| Rule | Case | Message names |
|---|---|---|
HDR-003 |
A file ends .trs |
The near-miss, in words, plus the .ttrs filename it was probably meant to be. .trs is a different, widely used format one keystroke away, so it is never reported as a generic unknown file. |
HDR-003 |
Filename is not <basename>.<kind>.ttrs |
The expected shape. A doubled extension (.transitrix.yaml.ttrs) lands here — .ttrs replaces the YAML form in full and is never appended to it. |
HDR-003 |
The file is outside canon/views/documents/ |
Where it belongs, and where it was found. |
TTRS-001 |
No YAML front matter, or no string kind: in it |
That the header must declare the kind as well as the filename. |
TTRS-013 |
The header's kind: disagrees with the filename's kind segment |
Both values. Kept under its own code so a kind disagreement never reads as a wrong extension — the two have different fixes. |
Kinds are not notations. mrd, srs, sdd, … are the middle segment of the
filename — one notation, one extension, one folder, with the kind as a value
inside it. The kind check is therefore that the filename and the header agree,
not that the kind is drawn from a registry; this repo deliberately keeps no
closed kind list to fall out of step with the methodology's.
Scope of the walk. The three zone folders (canon/, field/, codex/) plus
the repository's own top level — wide enough that a misplaced .ttrs is still
found (a walk scoped to the right folder could never see one), narrow enough that
a repository's own test fixtures and documentation are not read as model content.
Deliberately NOT ported: ACT-020 and DGCA-DEPR. DSM's Go importer
still accepts the pre-2026-06-25 activities notation/root-key alias (for
action docs) and the activities root key (for dgca/fgca docs) as a
non-fatal warning. Studio's own validators (activities/validate.ts,
fgca/parse-canonical.ts) deliberately removed that leniency in PR #320
(refactor(notations): drop deprecated notation aliases fgca/fga/activities/activity-card) — deprecated notation values are now
rejected with an error, not a warning, with tests asserting the old alias is
"no longer accepted". Re-introducing ACT-020/DGCA-DEPR would reverse that
already-shipped decision, not fill a gap. Flagged for a call rather than
silently reversed either way.
Deliberately NOT ported at repo-scope: HDR-001/002 and
LIFECYCLE-001/004 for capabilities. DSM's ValidateCapMap
(capabilities.go) validates the capability-map view document (the
inline capability_map.capabilities[] tree), not a standalone element — so
these are ported into packages/diagrams/src/capability-map/validate.ts
(the --scope=file/view-sweep validator for that notation), alongside the
notation's own pre-existing CMAP-* codes, rather than into
check-element-hygiene.ts. HDR-001/002 duplicate CMAP-001's existing
notation check under DSM's own code (so DSM can map a finding straight back
onto its import-log taxonomy); LIFECYCLE-001/004 (valid_from/valid_to
per CONTRACT.md §7) are net new — this notation's own schema
(capability-map/types.ts) does not track lifecycle dates today, tracking
target_date instead, but real documents (e.g. organizations/acme_corp)
author valid_from/valid_to inline per §7 regardless, so the checks read
them directly off the raw untyped node.
CONTRACT.md §9's versioned-attribute sidecar (<primitive_id>.history.yaml,
co-located under canon/elements/**) — packages/diagrams/src/ versioned-attribute/ (the parse/resolve/validate substrate, notation-agnostic)
plus packages/diagrams/src/repo-validate/check-versioned-attributes.ts (the
repo-scope wiring).
| Rule | Severity | Checks |
|---|---|---|
VERSIONED-001 |
error | A sidecar's target does not resolve to an admitted primitive in this model. |
VERSIONED-002 |
error | Two or more entries within one attribute's array carry the same valid_from. |
VERSIONED-003 |
warning | An attribute's array is not sorted by valid_from ascending. |
VERSIONED-004 |
error | A field declared time_varying is present inline on its primitive instead of only in the sidecar. |
VERSIONED-005 |
error | A version entry's valid_from falls outside [target.valid_from, target.valid_to]. |
VERSIONED-004's element-level field list (TIME_VARYING_FIELDS in
check-versioned-attributes.ts) enforces capability's current_maturity,
target_maturity, owner_role, target_date — every field methodology's
merged notations/views/05-capability-map.md declares time_varying, now
that PR #425 (the 2026-08-02 maturity ADR's remaining half) is on main;
organizations/acme_corp's capability fixtures migrated their inline
target_maturity values to sidecar form in the same pass.
Applications-catalogue owner_role/vendor/maturity are covered too,
but by a different mechanism. DSM's importer-parity element-file check
above walks standalone primitives under canon/elements/**; an
applications-catalogue entry has no file of its own — the time-varying
values, per notations/views/10-applications.md §5/§5a, are asserted
inline on the catalogue view document's applications[].{owner_role, vendor,maturity}, not on a standalone APPLICATION-* element file. So this
VERSIONED-004 instance is ported into
packages/diagrams/src/applications/validate.ts (the per-file notation
validator, same as HDR-001/002/LIFECYCLE-001/004 are for capability-map
above) rather than into check-versioned-attributes.ts's element sweep. The
sidecar itself is still co-located with the referenced APPLICATION-*
element file (canon/elements/03_application/applications/<app_id>. history.yaml) and its own shape (VERSIONED-001/002/003/005) is validated
generically by the existing element-sweep checkSidecars — only the
inline-vs-sidecar placement check needed a second home.
Rendering a current value from the sidecar — resolved for capability-map,
still open for applications. render-applications.ts
(a.maturity/a.vendor/a.owner_role) still reads the field directly off
the document it's given — no sidecar resolution wired in yet.
render-capability-map.ts and capability-map/resolve-maturity.ts (new)
now resolve current_maturity/target_maturity/owner_role/target_date
from the referenced CAPABILITY-* primitive's .history.yaml sidecar at a
date, via versioned-attribute/resolve.ts
(resolveAttributes/resolveAttributeValue, CONTRACT.md §9.2) — a display
fallback only: an inline value on the document always wins, nothing here
writes the resolved value back anywhere. The host-neutral
renderCapabilityMapHtml/renderCapabilityTreeSvg path takes an optional
resolution the caller supplies (mirrors entry.ts's single-parsed-document
shape — no folder access there, so nothing is resolved on that path); the
VS Code extension host (extension/src/capability-map-preview.ts) is the
one place with canon/elements/** access, so it's the one that actually
reads sidecars and resolves, the same posture applications-catalogue's
render-time resolution will need once it lands.
Not covered here: the native capability-map notation's own single-file
view-document form still requiring current_maturity inline. Its worked
examples (organizations/acme_corp/canon/views/capabilities/ compliance-domain.capability-map.transitrix.yaml, .templates/ capability-map_template.yaml) and its CMAP-003/005 validator all still
require current_maturity inline on the view document's own capability
nodes — which is itself inconsistent with CONTRACT.md §9's "not inline on the
capability-map view or on the element file" wording, predating this
epic. The render-time question this raised is settled (the 2026-08-05
packages decision): a nested capability-map entry references the
CAPABILITY-* primitive by id, so maturity resolves through that
primitive's own sidecar — no new per-nested-entry sidecar convention, same
shape as the DSM-migration CAPABILITY-V1.yaml + .history.yaml pairing,
and what the resolution above now implements. What remains open is only
whether CMAP-003 should stop requiring current_maturity inline (a
validator-tightening question, not a render-pipeline one) — not guessed at
here.
ELEMENT_PRIMITIVES.md §7.29 (methodology v3.6.0) closes extraction_confidence
out of canon for every TYPE, not only for RELEASE where the paragraph sits: it
is a review flag on an ingest candidate, surfaced in the review queue and
used for reviewer-authority routing (CONTRACT.md §6.2), and it is never
persisted into canon. Implemented in
packages/diagrams/src/repo-validate/check-candidate-fields.ts.
| Rule | Severity | Checks |
|---|---|---|
ADMIT-009 |
error | An admitted (zone: canon) element carries a candidate-only field (extraction_confidence), of any TYPE. |
Candidate selection is the admission marker, not a notation list — zone: canon (ELEMENT_PRIMITIVES.md §3, a required envelope field) is what makes the
field wrong, so the check reads it directly rather than enumerating TYPEs it
would have to keep in step with §4. It says nothing about zone: field or zone: codex artefacts, where harvest metadata legitimately lives (source_quality is
that zone's own provenance field, CONTRACT.md §5/§11.2), nor about a candidate
before admission. Sidecars carry neither zone nor id and fall out for free.
The message names the replacement, not just the defect — an element admitted
from extracted evidence cites it through derived_from (§3) pointing at an
OBSERVATION or another Field/Codex artefact, which is the pattern §7.29's own
fix (transitrix-hq#197) put in place.
The rule code was Studio-authored, ELEM-CANDIDATE-FIELD-001, following the
shape methodology already uses for a named element-envelope rule outside the
numbered run (ELEM-ALIAS-001, ELEM-FORMER-ID-001) and deliberately avoiding
ELEM-006 — the numbered ELEM-* run is methodology's to extend. Renamed to
ADMIT-009 (transitrix/methodology#505, merged 2026-08-19) now that methodology
has registered a code of its own for §7.29 — no functional change.
Scope: repo only, for now. --scope=file has no cross-TYPE envelope pass —
it dispatches to a per-notation validator, and several element TYPEs (release
among them, the one §7.29 is written against) have no file-scope validator wired
yet, so a check added there would cover the rule unevenly. Whole-canon scope
covers every TYPE uniformly today; a file-scope half is a follow-up, not a gap
this rule leaves in --scope=repo.
Repo-scope validation also sweeps the compliance notation surface with the same validators the VS Code preview uses:
| Path | File-scope | Repo-scope cross-document |
|---|---|---|
canon/elements/**/REQUIREMENT-*.yaml |
REQ-* shape |
REQ-002/003 with scanned CanonCatalog |
canon/elements/**/CONSTRAINT-*.yaml |
CONST-* shape |
CONST-004/005 with scanned CanonCatalog |
canon/assertions/ASSERTION-*.yaml |
ASSERT-* shape |
ASSERT-002..005 + ASSERT-007/008 warnings |
canon/verifications/VERIFICATION-*.yaml |
VERIF-* shape |
VERIF-002/005 with scanned CanonCatalog + REQ-VERIF-COVERAGE-001/002 warnings |
canon/elements/**/RISK-*.yaml |
RISK-* shape + RISK-COVERAGE-001 warning |
RISK-003/004 with scanned CanonCatalog |
canon/elements/**/METRIC-*.yaml |
METRIC-* shape |
METRIC-002/004 with scanned CanonCatalog |
canon/elements/**/NEED-*.yaml |
NEED-001 shape |
NEED-002 with scanned CanonCatalog + NEED-COVERAGE-001/NEED-VALIDATION-COVERAGE-001/002 warnings |
canon/validations/VALIDATION-*.yaml |
VALID-* shape |
VALID-002/005 with scanned CanonCatalog |
codex/** |
CODEX-* |
folder jurisdiction (CODEX-005) |
canon/views/**.compliance-impact.* |
COMPIMP-001 parse |
COMPIMP-REF, COMPIMP-009..011, buildImpactMatrix |
canon/views/**.coverage-metric.* |
COVMET-* parse |
COVMET-REF, buildCoverageMatrix |
Derived views (no YAML config file) — compliance-matrix, gap-dashboard,
single-law, single-product, requirement-trace — are built interactively in
the preview / export-compliance; they are not separate file validators.
VERIFICATION (27-verification.md) — the engineering V&V analogue of
ASSERTION. VERIF-001..006 are the single-file
shape rules (mirrors ASSERT-001..008 — no staleness rule, since a
verification has no next_review_at). The reverse-trace completeness check —
"does every REQUIREMENT have a verification, and has it closed?" — is
cross-cutting, computed the same way as GAP-REQ-NO-ASSERT:
| ruleId | Fires when |
|---|---|
REQ-VERIF-COVERAGE-001 |
No admitted VERIFICATION carries verifies: <this REQUIREMENT id>. |
REQ-VERIF-COVERAGE-002 |
One or more VERIFICATIONs target the requirement, but every one is still not_yet_run/inconclusive — none has reached pass/fail. Mutually exclusive with -001 by construction. |
Both are warning-severity (a newly admitted REQUIREMENT legitimately has no
verification yet), surfaced in the compliance findings bucket alongside
GAP-REQ-NO-ASSERT. The Studio extension surfaces the same verdict where the
author is working, not only in a report: the Requirement Trace preview
(opened from a REQUIREMENT-*.yaml/CONSTRAINT-*.yaml file) renders a
"Verification" section that reads as an open obligation — labelled "Not
verified" or "Unresolved" — rather than a blank section when the gap exists;
the Compliance Gap Dashboard lists every affected requirement repo-wide.
Both re-scan on save of any VERIFICATION-*.yaml file. The core validator is
the single source of the verdict — the previews render buildGapReport's
output, they never compute their own judgement.
RISK / METRIC / NEED / VALIDATION — the methodology 3.1.0 vocabulary
follow-on (ELEMENT_PRIMITIVES.md §7.26–§7.28, §9; 15-requirement.md
§2.5–§2.7; 28-validation.md). RISK-001..004, METRIC-001..004, and
NEED-001..002 are single-file shape/resolution rules for the three new
standalone motivation-layer elements, following the same
validate<Notation>(input, { catalog }) shape as validateRequirement /
validateVerification. VALID-001..006 is the shape/resolution suite for
VALIDATION — the validation-domain claim type anchored on NEED, structured
the same way as VERIFICATION but one layer further upstream (§28
§1/§4). REQUIREMENT grew three matching fields: level (REQ-005, the
ISO/IEC/IEEE 29148 StRS/SyRS/SRS tier), kind (REQ-006, functional vs
quality), and serves (REQ-SERVES-001, tracing back to the upstream NEED
it satisfies).
RISK-COVERAGE-001 (an untreated risk — empty/absent treated_by) is a
single-file check, unlike the other *-COVERAGE-* rules: it reads only the
RISK element's own field, no catalogue scan required. NEED carries the
same cross-cutting reverse-trace shape as REQUIREMENT/VERIFICATION,
computed the same way as GAP-REQ-NO-ASSERT / REQ-VERIF-COVERAGE-*:
| ruleId | Fires when |
|---|---|
NEED-COVERAGE-001 |
No admitted REQUIREMENT carries serves: <this NEED id>. |
NEED-VALIDATION-COVERAGE-001 |
No admitted VALIDATION carries validates: <this NEED id>. |
NEED-VALIDATION-COVERAGE-002 |
One or more VALIDATIONs target the need, but every one is still not_yet_run/inconclusive — none has reached pass/fail. Mutually exclusive with -001 by construction. |
All three are warning-severity, same posture as REQ-VERIF-COVERAGE-* (a
newly admitted NEED legitimately has no serving requirement or validation
yet), surfaced in the compliance findings bucket. There is no dedicated
NEED/VALIDATION IDE preview surface yet (28-validation.md §7), and the
Compliance Gap Dashboard export/preview (compliance/html.ts,
compliance/markdown.ts, extension/src/gap-dashboard-preview.ts) render
GapReport's requirementsWith* fields explicitly and were not extended for
the new needsWith* fields in this pass — buildGapReport computes them
(consumed today by runGapDashboardWarnings for --scope=repo's
compliance findings), but surfacing them in the dashboard's own sections is
follow-up work, not yet wired.
File-scope transitrix validate <requirement.yaml> runs shape rules only unless
you pass a catalogue (repo-scope builds one automatically from canon/** +
codex/**).
$ transitrix validate --scope=repo --root organizations/acme_corp
✓ organizations/acme_corp — repo-scope validation passed
The richer target/category finding taxonomy is intentionally not
adopted yet (deferred per the ADR until a consumer needs it). Two fields are
un-frozen: ruleId — a stable code on findings that have one (see the
strategy-chain rules above, plus TSVC-003/INT-002) — and severity
('error' | 'warning', omitted meaning 'error') — needed so DSM's
warn-severity strategy-chain rules could be ported without becoming blocking
findings. target/category stay out.
transitrix validate --scope=repo --json --include-model adds a model key
alongside the findings — the resolved canon/elements/** and
canon/relations/** records the repo-scope walk already parsed, for a
non-JS consumer (e.g. DSM's Go backend) that wants the parsed model without
re-implementing the notation schema. Off by default — --json without
--include-model keeps the existing output shape unchanged.
{
"scope": "repo",
"root": "organizations/acme_corp",
"valid": true,
"findings": [],
"views": { "valid": true, "findings": [] },
"codex": { "valid": true, "findings": [] },
"compliance": { "valid": true, "findings": [] },
"skipped": [],
"model": {
"elements": [
{
"id": "DRIVER-COMP-1",
"name": "Support response time",
"notation": "driver",
"type": "internal",
"layer": "motivation",
"sourceFile": "canon/elements/01_motivation/factors/DRIVER-COMP-1.yaml",
"data": {
"notation": "driver",
"id": "DRIVER-COMP-1",
"name": "Support response time",
"type": "internal",
"description": "Standing internal driver — the performance dimension of support first-response time. …",
"zone": "canon",
"admitted_at": "2026-05-29",
"admitted_by": "v.korobeinikov",
"gate_checks": { "uniqueness": "pass", "consistency": "pass", "completeness": "pass" },
"valid_from": "2026-05-26",
"valid_to": null
}
}
],
"relations": [
{
"id": "REL-EMP-PERSON-OPS-1",
"kind": "employment",
"source": "ACTOR-PERSON-1",
"target": "ACTOR-OPS-1",
"sourceFile": "canon/relations/REL-EMP-PERSON-OPS-1.yaml"
}
]
}
}- Elements —
id,name(''if absent),notation(the element TYPE's short name, e.g.driver,goal,capability),type(per-TYPE subtype, omitted when the doc has none),layer(from the doc'slayerfield, else derived from thecanon/elements/<NN>_<layer>/…folder — omitted if neither is present),sourceFile(path relative to--root),data— the complete parsed element document, unfiltered (every canon-authored field the file has, plus the admission/lifecycle envelope).id/name/notation/type/layer/sourceFilestay a minimal, stable identity projection for consumers that only need those;datais the faithful projection for a consumer that needs a field the engine already parsed but the minimal set doesn't carry (a goal'slevel/parent/link/tags, an action's scheduling and ownership fields, …) — without the engine growing a bespoke field list per consumer. - Relations —
id(''if the doc has none),kind(the relation'stypefield, omitted when absent),source/target(resolvedfrom/to, or the legacysource/targetkeys). A relation is only emitted once both endpoints resolve to a non-empty id — an endpoint that fails to resolve is already surfaced as a referential-integrity finding above; this projection does not duplicate that as a half-resolved record.
Rules are organized by element category using stable ID prefixes:
- SE-NNN: Structural Elements (pool, lanes, swimlanes)
- EE-NNN: Event Elements (start, end, intermediate catch/throw)
- GW-NNN: Gateway Elements (XOR, AND, OR, event-based, etc.)
- ACT-NNN: Activity Elements (tasks, sub-processes, call activities)
- SF-NNN: Sequence Flows (connections, routing, edge cases)
- CONN-NNN: Connections and Ports (entry/exit port validation, overlap detection)
- AP-NNN: Anti-patterns (deadlocks, unreachable paths, livelock risk, etc.)
Each finding has a severity level indicating its impact:
| Severity | Meaning | Examples |
|---|---|---|
| error | Blocking issue; invalid BPMN 2.0 or structural error. | Missing start event, invalid element type, duplicate IDs |
| warning | Advisory; conformance risk or style issue. | Unused element, risky gateway configuration, naming convention |
| info | Diagnostic; metrics, hints, or quality suggestions. | Layout density, performance estimate, style suggestion |
Pool existence is enforced by the AJV schema during YAML parsing (layer L1). All BPMN 2.0 processes require at least one pool; the parser rejects any DSL that does not define a pool.
Semantic validation for BPMN 2.0 start event constraints per method/methodology.md Section 7.
- Severity: error
- Description: A BPMN 2.0 process must have at least one start event to define an entry point.
- BPMN Rule: Mandatory per BPMN 2.0 execution semantics (method/methodology.md SE-01).
- Implementation: Scans all lanes for elements with
type === 'startEvent'. Fails if count is zero. - Remediation: Add a start event element to the first lane. In BPMN YAML:
type: startEvent. - Example Finding:
{ "ruleId": "SE-001", "severity": "error", "message": "Process must have at least one start event", "hint": "Add a start event to begin process flow" }
- Severity: error
- Description: Sequence flows cannot target start events; they are process entry points with no predecessors.
- BPMN Rule: Per BPMN 2.0 specification (method/methodology.md SE-03), start events have no incoming flows.
- Implementation: For each start event, checks if any flow has
to === startEvent.id. Fails if found. - Remediation: Remove sequence flows that terminate at the start event. Start events are entry points only.
- Example Finding:
{ "ruleId": "SE-003", "severity": "error", "elementId": "start-1", "message": "Start event \"Process Start\" must not have incoming flows", "hint": "Remove flows targeting this start event" }
- Severity: error
- Description: Each start event must have exactly one outgoing flow to initiate the process with deterministic routing.
- BPMN Rule: Per BPMN 2.0 specification (method/methodology.md SE-04), a start event is a single point of entry.
- Implementation: For each start event, counts flows with
from === startEvent.id. Fails if count ≠ 1. - Remediation:
- If 0 outgoing: Connect the start event to the first activity with a sequence flow.
- If >1 outgoing: Use a gateway (XOR, AND, etc.) after the start event for branching logic.
- Example Findings:
{ "ruleId": "SE-004", "severity": "error", "elementId": "start-1", "message": "Start event \"Process Start\" must have exactly one outgoing flow (found 0)", "hint": "Add a flow from this start event to the first process activity" }{ "ruleId": "SE-004", "severity": "error", "elementId": "start-1", "message": "Start event \"Process Start\" must have exactly one outgoing flow (found 2)", "hint": "Remove 1 extra flow(s) from this start event" }
Semantic validation for BPMN 2.0 end event constraints per method/methodology.md Section 7.
Numbering note: Code implements end-event rules as EE-001, EE-003, EE-004 (no EE-002). Per BPMN 2.0 spec, these map to spec IDs EE-01, EE-02, EE-03 respectively (removed stub EE-002 in RD-128). The 3-digit format allows room for Transitrix-specific sub-rules without colliding with future spec extensions.
- Severity: error
- Description: A BPMN 2.0 process must have at least one end event to define process termination.
- BPMN Rule: Mandatory per BPMN 2.0 execution semantics (method/methodology.md EE-01).
- Implementation: Scans all lanes for elements with
type === 'endEvent'. Fails if count is zero. - Remediation: Add an end event element to a lane. In BPMN YAML:
type: endEvent. - Example Finding:
{ "ruleId": "EE-001", "severity": "error", "message": "Process must have at least one end event", "hint": "Add an end event to define process termination" }
- Severity: error
- Description: Sequence flows cannot originate from end events; they are process termination points.
- BPMN Rule: Per BPMN 2.0 specification (method/methodology.md EE-03), end events have no outgoing flows.
- Implementation: For each end event, checks if any flow has
from === endEvent.id. Fails if found. - Remediation: Remove sequence flows that originate from the end event. End events are exit points only.
- Example Finding:
{ "ruleId": "EE-003", "severity": "error", "elementId": "end-1", "message": "End event \"Process End\" must not have outgoing flows", "hint": "Remove flows originating from this end event" }
- Severity: error
- Description: Each end event must have at least one incoming flow to receive process execution flow.
- BPMN Rule: Per BPMN 2.0 specification (method/methodology.md EE-04), process execution must reach an end event.
- Implementation: For each end event, counts flows with
to === endEvent.id. Fails if count is zero. - Remediation: Connect the final process activity to this end event with a sequence flow.
- Example Finding:
{ "ruleId": "EE-004", "severity": "error", "elementId": "end-1", "message": "End event \"Process End\" must have at least one incoming flow", "hint": "Connect this end event to the final process activity with a sequence flow" }
Each rule is a ValidationRule object with 4 required fields:
import type { ValidationRule, ValidationFinding } from '../src/validator-types.js'
import type { ProcessIr } from '../src/ir.js'
const rule_XX_NNN: ValidationRule = {
ruleId: 'XX-NNN', // Unique rule ID (SE-, EE-, GW-, etc.)
severity: 'warning', // 'error' | 'warning' | 'info'
description: 'What this rule checks',
validate(ir: ProcessIr): ValidationFinding[] {
const findings: ValidationFinding[] = []
// Inspect ir.lanes, ir.flows, etc.
// Push findings if violations detected.
return findings
}
}const rule_EE_001: ValidationRule = {
ruleId: 'EE-001',
severity: 'error',
description: 'All events use valid BPMN 2.0 event type values',
validate(ir: ProcessIr): ValidationFinding[] {
const findings: ValidationFinding[] = []
const validEventTypes = ['startEvent', 'endEvent']
for (const lane of ir.lanes) {
for (const el of lane.elements) {
if (el.type.includes('Event') && !validEventTypes.includes(el.type)) {
findings.push({
ruleId: 'EE-001',
severity: 'error',
elementId: el.id,
message: `Event "${el.name}" has invalid type "${el.type}"`,
hint: `Use one of: ${validEventTypes.join(', ')}`,
})
}
}
}
return findings
}
}Add the rule to the global registry in src/validator.ts:
validator.register(rule_XX_NNN)Create a test in tests/validator.test.ts:
test('EE-001: detects invalid event types', () => {
const ir: ProcessIr = {
id: 'test',
name: 'Test',
poolId: 'pool-1',
poolName: 'Pool',
lanes: [{
id: 'lane-1',
name: 'Lane 1',
elements: [{
id: 'event-1',
name: 'Bad Event',
type: 'invalid-type', // Invalid!
poolId: 'pool-1',
laneId: 'lane-1',
}],
}],
flows: [],
}
const report = validateProcess(ir)
expect(report.findings).toContainEqual(
expect.objectContaining({
ruleId: 'EE-001',
severity: 'error',
elementId: 'event-1',
})
)
})Before submitting a rule:
- Rule ID follows category prefix (SE-, EE-, GW-, ACT-, SF-, CONN-, AP-)
- Severity is appropriate (error = blocking, warning = advisory, info = diagnostic)
-
validate()function handles empty lanes/elements gracefully - Finding includes
ruleId,severity,message; optionalelementId,hint,docUrl - At least one test case covers the rule
- Test includes both pass and fail scenarios
- Rule is registered in
src/validator.ts - Documentation added to this file under appropriate category
Validation findings are included in HTTP API responses:
Response:
{
"xml": "...",
"metrics": { ... },
"validation": {
"isValid": true,
"findings": [],
"summary": {
"errorCount": 0,
"warningCount": 0,
"infoCount": 0
}
}
}The transitrix compile command includes validation in its output:
$ npm run transitrix -- compile example.bpmn.transitrix.yaml out.bpmn
✓ Compiled: out.bpmn
✓ Validation: 0 errors, 0 warnings
- RD-096 (Foundation): Baseline validator registry pattern; pool existence enforced by AJV schema.
- RD-097+: Semantic rules for events, gateways, activities, anti-patterns.
- Cross-lane validation: Validators can access lane indices for cross-lane checks.
- Metrics in findings: Some rules may surface layout metrics as info findings.
- Baseline violation tracking (which diagrams violate which rules over time)
- Severity thresholds (treat warnings as errors above threshold)
- Custom rule loading from external plugins
- Detailed remediation guides linked from findings
Semantic validation for BPMN 2.0 sequence flow constraints, including routing restrictions and duplicate detection.
- Severity: error
- Description: Sequence flows must have unique source-target pairs; no two flows can connect the same from and to elements.
- BPMN Rule: Per BPMN 2.0, redundant flows are not semantically meaningful and indicate a modeling error.
- Implementation: Builds a map of "from→to" pairs. Fails if any pair appears more than once.
- Remediation: Remove one of the duplicate flows. Only one sequence flow can directly connect the same two elements.
- Example Finding:
{ "ruleId": "SF-DUP", "severity": "error", "elementId": "flow-2", "message": "Duplicate flow from \"task-1\" to \"task-2\" (first: flow-1)", "hint": "Remove one of the duplicate flows" }
- Severity: error
- Description: All sequence flows must have valid from and to endpoints that reference existing process elements.
- BPMN Rule: Per BPMN 2.0, flows connect existing elements within the process. Invalid references are structural errors.
- Scope: Since Transitrix Studio enforces single-pool-per-file, this validates that endpoints exist within the pool's lanes.
- Implementation: For each flow, checks if both
fromandtoelement IDs exist in any lane of the process. - Remediation:
- If source missing: Verify the source element ID is correct and exists.
- If target missing: Verify the target element ID is correct and exists.
- Example Findings:
{ "ruleId": "SF-001", "severity": "error", "elementId": "flow-1", "message": "Flow source element \"missing-task\" does not exist", "hint": "Verify the source element ID is correct and exists in the process" }{ "ruleId": "SF-001", "severity": "error", "elementId": "flow-2", "message": "Flow target element \"nonexistent-end\" does not exist", "hint": "Verify the target element ID is correct and exists in the process" }
- Severity: error
- Description: Sequence flows with condition expressions are only allowed when sourced from Activity elements (tasks: task, userTask, serviceTask) or XOR (exclusive) / inclusive gateways. Conditions on flows from other element types (events, parallel gateways, etc.) are not evaluated and indicate a modeling error.
- BPMN Rule: Per BPMN 2.0 specification, conditions are evaluated at split points (XOR/inclusive gateways and activities with outgoing conditional flows).
- Implementation: For each flow with a condition expression, checks that the source element type is one of:
task,userTask,serviceTask,exclusiveGateway,inclusiveGateway. - Remediation: Either (a) remove the condition expression from the flow, or (b) ensure the flow originates from an Activity or XOR gateway.
- Example Finding:
{ "ruleId": "SF-005", "severity": "error", "elementId": "flow-bad", "message": "Flow \"flow-bad\" with condition cannot originate from \"startEvent\"", "hint": "Condition expressions are only allowed on flows from Activities (task, userTask, serviceTask) or XOR gateways" }
- Severity: error
- Description: The default flow marker (the flow that routes tokens when no conditions match) is only valid on flows sourced from Activity elements or XOR / inclusive gateways. Other element types do not have conditional branching semantics.
- BPMN Rule: Per BPMN 2.0, the default attribute is evaluated at split points (XOR/inclusive gateways and conditional activities).
- Implementation: For each flow marked as default (
flow.default === true), checks that the source element type is one of:task,userTask,serviceTask,exclusiveGateway,inclusiveGateway. - Remediation: Remove the default marker from the flow, or move the flow to originate from an Activity or XOR gateway.
- Example Finding:
{ "ruleId": "SF-006", "severity": "error", "elementId": "flow-default", "message": "Flow \"flow-default\" marked as default cannot originate from \"parallelGateway\"", "hint": "Default flow marker is only allowed on flows from Activities (task, userTask, serviceTask) or XOR gateways" }
- Severity: error
- Description: A single sequence flow cannot be marked as both default and conditional. The default flow is the fallback when all conditions are false; a condition on the default flow is contradictory.
- BPMN Rule: Per BPMN 2.0 semantics, default and conditional are mutually exclusive routing attributes.
- Implementation: For each flow, checks that
flow.defaultandflow.conditionare not both true simultaneously. - Remediation: Choose one: either mark the flow as default (remove condition), or give it a condition expression (remove default marker).
- Example Finding:
{ "ruleId": "SF-007", "severity": "error", "elementId": "flow-conflict", "message": "Flow \"flow-conflict\" cannot have both default marker and condition expression", "hint": "A flow must be either the default route (default: true) or have a condition, not both" }
Semantic validation for BPMN 2.0 intermediate (catch) events — intermediateMessageEvent, intermediateTimerEvent.
- Severity: error
- Description: Intermediate catch events sit mid-flow, so they must have at least one incoming and at least one outgoing flow. A missing endpoint strands the token. This is distinct from start events (no incoming) and end events (no outgoing), which are intentionally one-sided.
- BPMN Rule: Per BPMN 2.0, intermediate catch events have exactly one incoming and one outgoing sequence flow in normal (non-boundary) usage.
- Implementation: For each intermediate event, counts incoming and outgoing flows; fails if either is zero.
- Remediation: Connect the intermediate event to both a predecessor and a successor.
- Example Finding:
{ "ruleId": "IE-001", "severity": "error", "elementId": "wait", "message": "Intermediate event \"Wait\" must have incoming and outgoing flows (no outgoing)", "hint": "Connect a flow from this intermediate event to the next element" }
Semantic validation for BPMN 2.0 gateway element constraints per method/methodology.md Section 7.
- Severity: error
- Description: An exclusive (XOR) gateway with only one incoming and one outgoing flow is a no-op routing element. It neither splits nor joins control flow; use a direct sequence flow instead.
- BPMN Rule: Per BPMN 2.0, gateways are for routing logic (branching or synchronization). A single-in single-out configuration serves no routing purpose.
- Implementation: For each XOR gateway, counts incoming and outgoing flows. Fails if both counts equal 1.
- Remediation: Either remove the gateway and use a direct flow between the two connected elements, or add additional outgoing flows if the gateway is intended as a split point.
- Example Finding:
{ "ruleId": "GW-XOR-01", "severity": "error", "elementId": "gateway-1", "message": "XOR gateway \"Decision\" has single incoming and single outgoing flow", "hint": "Use a direct flow instead of a single-in single-out gateway; XOR is for routing decisions" }
GW-XOR-02: XOR split outgoing flows must have at most one default and all others must be conditional (RD-105)
- Severity: error
- Description: When an XOR gateway splits (has multiple outgoing flows), control flow routing must be fully specified: at most one flow is marked as default (the fallback), and all other flows must have explicit condition expressions. This ensures deterministic routing.
- BPMN Rule: Per BPMN 2.0, XOR split semantics require that for any input token, exactly one outgoing flow is taken. This is achieved by conditions + default.
- Implementation: For each XOR gateway with ≥2 outgoing flows:
- Counts default flows; fails if count > 1.
- Checks all non-default flows have a condition; fails if any are unconditional.
- Remediation:
- If too many defaults: keep only one, remove the rest.
- If unconditional flows: add condition expressions to all non-default flows.
- Example Findings:
{ "ruleId": "GW-XOR-02", "severity": "error", "elementId": "xor-split", "message": "XOR split \"Approve Or Reject\" has 2 default flows (max 1)", "hint": "Mark at most one outgoing flow as default; others must have explicit conditions" }{ "ruleId": "GW-XOR-02", "severity": "error", "elementId": "xor-split", "message": "XOR split \"Route Order\" has 2 flow(s) without condition or default", "hint": "All outgoing flows must either have a condition or be marked as default (but max 1 default)" }
- Severity: error
- Description: Parallel (AND) gateways route all tokens to all outgoing branches simultaneously; conditional branching has no meaning in parallel execution. Flows from a parallel split cannot have conditions or be marked as default.
- BPMN Rule: Per BPMN 2.0 execution semantics, AND splits are unconditional and non-exclusive.
- Implementation: For each parallel gateway, checks all outgoing flows for condition expressions. Fails if any flow has
flow.conditionset. - Remediation: Remove condition expressions from all outgoing flows of parallel gateways. If selective branching is needed, use an XOR gateway instead.
- Example Finding:
{ "ruleId": "GW-AND-04", "severity": "error", "elementId": "parallel-split", "message": "Parallel gateway \"Fork Tasks\" has 1 outgoing flow(s) with condition", "hint": "Parallel gateways route all tokens to all branches; conditions are not evaluated" }
Semantic validation for BPMN 2.0 task (activity) constraints per method/methodology.md Section 7.
- Severity: error
- Description: Every task must have at least one incoming flow (predecessor) and one outgoing flow (successor) to define its entry and exit points in the process flow.
- BPMN Rule: Per BPMN 2.0 specification, tasks are activities that consume input and produce output within the process.
- Exception: Sole-element process (single task with no other elements) is exempt from this rule.
- Implementation: For each task element, counts incoming flows (
to === task.id) and outgoing flows (from === task.id). Fails if count < 1 for either direction (unless sole-element process). - Remediation:
- If missing incoming: Connect a flow from the previous activity (or start event) to this task.
- If missing outgoing: Connect a flow from this task to the next activity (or end event).
- If both missing: Connect the task into the flow path between predecessors and successors.
- Example Finding:
{ "ruleId": "ACT-001", "severity": "error", "elementId": "task-1", "message": "Task \"Verify Order\" must have incoming and outgoing flows (no outgoing)", "hint": "Connect a flow from this task to the next activity" }
Semantic validation for process connectivity constraints per method/methodology.md Section 7.
- Severity: error
- Description: Every element in the process must be reachable from at least one start event and must have a path leading to at least one end event. This ensures the process has no unreachable (dead) code or hanging elements.
- BPMN Rule: Per BPMN 2.0 execution semantics, all activities must participate in the flow from entry to exit.
- Implementation: Performs forward reachability (BFS from all start events) and backward reachability (BFS from all end events). An element fails if it is unreachable from any start OR cannot reach any end.
- Remediation: Connect the unreachable element to the flow path, or remove it if it is not part of the process.
- Example Finding:
{ "ruleId": "CONN-001", "severity": "error", "elementId": "orphan-task", "message": "Element cannot reach any end event", "hint": "Connect this element to a flow path leading to an end event" }
- Severity: error
- Description: The process must form a single connected graph with no isolated subgraphs (islands). Every element must have a path (direct or transitive) to every other element when edges are treated as undirected.
- BPMN Rule: BPMN 2.0 processes are single-threaded flow graphs; multiple disconnected islands indicate a modeling error.
- Implementation: Treats all flows as undirected edges, performs DFS/BFS to verify all elements are reachable from the first element.
- Remediation: Connect isolated elements or islands to the main process graph via sequence flows.
- Status: Fully covered by CONN-001 + parser validation.
- Rationale: The parser validates all
flow.toreferences exist (no dangling flows). CONN-001 ensures every element can reach an end event, so every flow source has a path through its target to an exit. Thus CONN-003 (transitive reachability of flow targets) requires no separate rule.
Anti-pattern rules detect suspicious modeling practices that may indicate errors, deadlock risks, or livelock scenarios. Unlike error rules, anti-patterns are warnings or info and can be enabled/disabled via .transitrixrc (see Configuring rules).
- Severity: warning
- Description: An element (task, gateway, or event) that is completely disconnected from the process flow — it has neither incoming nor outgoing flows. This is typically a modeling error: either the element should be connected to the flow, or it should be deleted.
- Distinct from ACT-001: ACT-001 enforces that tasks have both incoming and outgoing flows as a hard rule. AP-FLOAT flags any element that is floating, which is broader and is a warning.
- Implementation: For each element, counts incoming and outgoing flows. Flags if both counts are zero.
- Default: enabled (warning).
- Remediation: Either connect the element to the process flow via incoming and/or outgoing flows, or delete it if it is not part of the intended design.
- Example Finding:
{ "ruleId": "AP-FLOAT", "severity": "warning", "elementId": "task-orphan", "message": "Element \"Unused Task\" has no incoming or outgoing flows", "hint": "Connect this element to the process flow, or remove it if unused" }
- Severity: warning
- Description: An exclusive (XOR) gateway with multiple outgoing flows where every flow has a condition expression, but none is marked as default. If at runtime all conditions evaluate to false, the token has no exit route and the process deadlocks.
- Risk: Deadlock / token trapping.
- Distinct from GW-XOR-02: GW-XOR-02 is an error rule enforcing structural constraints (max 1 default, all others must be conditional). AP-NO-DEFAULT is a warning that detects a specific deadlock risk.
- Implementation: For each XOR gateway with ≥2 outgoing flows, checks if no default is marked AND all flows are conditional. Flags if both conditions are true.
- Default: enabled (warning).
- Remediation: Mark one of the outgoing flows as default (the fallback branch), or adjust one of the conditions to be catch-all (e.g.,
amount >= 0instead ofamount > 100). - Example Finding:
{ "ruleId": "AP-NO-DEFAULT", "severity": "warning", "elementId": "xor-decision", "message": "XOR split \"Approve Or Deny\" has all conditional flows but no default", "hint": "If all conditions are false, the token will be trapped; mark one flow as default or add a catch-all condition" }
- Severity: warning
- Description: A task element with more than one incoming flow but no explicit AND (parallel) or XOR join gateway. In BPMN 2.0 semantics, each incoming token independently activates the task, which may not be the intended behavior if synchronization is needed.
- Risk: Unintended parallelism; multiple simultaneous activations of the task.
- Implementation: For each task element (task, userTask, serviceTask), counts incoming flows. Flags if count > 1.
- Default: enabled (warning).
- Remediation: If synchronization is intended, insert an AND (parallelGateway) join gateway before the task to combine all incoming flows into a single synchronized flow. If parallel activation is intended, document the deliberate design.
- Example Finding:
{ "ruleId": "AP-IMPLICIT-JOIN", "severity": "warning", "elementId": "task-process", "message": "Task \"Process Request\" has 2 incoming flows (implicit join)", "hint": "Each incoming token independently activates the task; use an AND join gateway if synchronization is intended" }
- Severity: warning
- Description: A gateway element (XOR, AND, OR, event-based) whose name starts with an imperative verb typical of task names (e.g., "Validate", "Approve", "Check", "Generate"). This is a heuristic hint that the element might be a task incorrectly modeled as a gateway. Gateways are routing constructs; tasks perform work.
- Note: This is a heuristic hint, not a hard rule. Some gateways may legitimately have action-like names, especially in high-level process descriptions.
- Imperative verb list: accept, approve, assign, authorize, calculate, cancel, check, classify, confirm, convert, create, decline, delete, derive, determine, distribute, document, evaluate, execute, extract, generate, identify, implement, invoice, judge, log, manage, notify, organize, pay, perform, prepare, process, produce, propose, publish, read, receive, reconcile, record, reduce, register, reject, release, remove, report, request, resolve, review, revise, schedule, send, sign, store, submit, summarize, test, track, transfer, transform, validate, verify, write.
- Default: off by default (disabled). Enable via
.transitrixrc:"rules": { "AP-GW-AS-TASK": "warn" }. - Remediation: Review the gateway's purpose. If it performs work, replace it with a task element. If it is genuinely a routing construct, rename it to a descriptive noun (e.g., "Approval Decision" instead of "Approve Order").
- Example Finding:
{ "ruleId": "AP-GW-AS-TASK", "severity": "warning", "elementId": "gateway-1", "message": "Gateway \"Validate Form\" has a name starting with an imperative verb; ensure this is a routing construct, not a task", "hint": "Gateways are for routing logic only. If this element performs work, use a task instead." }
The canonical config file is
.transitrixrc(JSON). Legacy.cervinrcis not read (removed in 2.0.0).
.transitrixrc is a JSON file. The rules map enables or disables individual
rules by ID:
{
"rules": {
"AP-FLOAT": "off",
"AP-GW-AS-TASK": "warn"
}
}The only valid override values are "off" and "warn":
"off"— disable the rule. Error-severity rules are BPMN conformance gates and cannot be disabled:"off"on such a rule is rejected at load time."warn"— enable the rule. Use it to turn on an off-by-default rule (such asAP-GW-AS-TASK); for an already-enabled rule it is a no-op.
"warn"does not change a rule's severity. Every rule keeps its built-in severity (error or warning); an override only toggles whether the rule runs. Writing"SE-001": "warn"does not demote that error to a warning — to lower a rule's reported severity, change the rule definition, not the config. (Values such as"error"or"on"are not accepted and fail schema validation.)
Default state:
AP-FLOAT: warning, enabled by defaultAP-NO-DEFAULT: warning, enabled by defaultAP-IMPLICIT-JOIN: warning, enabled by defaultAP-GW-AS-TASK: warning, disabled by default — enable with"warn"
Association rules govern the associations array in the process DSL, which connects data objects to activities using dashed undirected edges (BPMN 2.0 <association>). Associations are distinct from sequence flows and do not participate in token routing.
- Severity: error
- Description: Each entry in
associationsmust have exactly onedataObjectendpoint and one activity endpoint (task,userTask, orserviceTask). Associations between two activities or two data objects are invalid — use sequence flows between activities, and model data dependencies asdataObject ↔ activityassociations only. - Example Finding:
{ "ruleId": "ASC-001", "severity": "error", "elementId": "assoc1", "message": "Association \"assoc1\" must connect a data object to an activity (found \"task\" → \"task\")", "hint": "Associations link data objects to activities. Use sequence flows between activities." }
- Severity: error
- Description: Both
fromandtoof an association must reference element IDs that exist in the process. This is a defensive check — the parser already validates endpoints at parse time. - Example Finding:
{ "ruleId": "ASC-002", "severity": "error", "elementId": "assoc2", "message": "Association source element \"missing\" does not exist", "hint": "Verify the source element ID is correct and exists in the process" }
Canonical and supported inline ACTION records use ACTION-007 through
ACTION-011 for unresolved references, predecessor cycles, self references,
planned dates, and negative numbers. Schedule ACT-009 is the missing-anchor
warning. Historical diagnostics are not rewritten or globally aliased.
Negative values in duration, duration_days, labor_cost, resources_cost,
effort, and score produce ACTION-011 only when the catalogue's
transitrix.yaml selects methodology 7.0.0 or later. Absent pins preserve earlier
numeric compatibility. A document's spec_version does not select this boundary.
Both duration fields are checked; scheduling still prefers duration.
sort has no new sign restriction. Scores and sort values must be integers;
other numeric fields allow finite fractions. Only duration fields allow null.
Type violations use SCHEMA_INVALID, including non-array predecessors.
Products and scenarios projections remain explicitly unvalidated, with
NOTATION-SKIP-001; strict validation rejects them. Inline shape validation
remains available. Missing external codex jurisdiction/effective date uses
CODEX-002, not the retired identity.
Inline DGCA requires nonempty collections (DGCA-004); array shape errors
retain FGCA-004. The Changes layer may be disabled. Resolved projections may
select empty collections without inheriting the inline nonempty requirement.
RISK likelihood, impact, and residual accept the existing low, medium,
and high values or finite numbers. Numeric degrees require a risk_scale
mapping with a nonempty id, finite inclusive min/max bounds (min < max),
and direction: higher or lower indicating which direction means more risk.
The adopter defines this ordinal scale; fractions and zero are allowed within
its bounds. The validator reports invalid degrees/scales as RISK-002 and
preserves authored values. It does not calculate risk or map numbers to words.
Current results use COMPIMP-003, COVMET-003, REL-002 and
DGCA-REPO-008 through DGCA-REPO-011 for their published reference rules.
Inline Applications, Products and Capability Map schema failures use
SCHEMA_INVALID with form, field, expected type and actual value. Products
projection codes are never reused for inline item status/type errors.
For catalogues selecting Methodology 5 or later, unreferenced drivers, goals
and changes appear in JSON observations and the human-readable coverage
section. They are no longer emitted under removed FGCA-012 through
FGCA-014 codes. Older/unversioned catalogue diagnostics retain their legacy
compatibility behavior; stored historical results are not rewritten.
Capability Map maturity and other time-varying attributes belong in the
capability's .history.yaml sidecar. Repo validation resolves current maturity
at capability_map.assessment_date; inline storage produces VERSIONED-004.
Missing required resolved maturity remains a schema error. Single-file
validation without the sidecar catalogue explicitly reports that check as
unvalidated, rather than demanding an inline copy. Existing inline maturity
examples are historical rejection fixtures, not current authoring templates.
The published scenarios[] set form is explicitly unvalidated until its
validator exists, as are unsupported projection forms. Strict repo validation
rejects these gaps. Successful file accounting is not a claim that every
published notation or form has a complete validator.
The CLI validates standalone assessment, capability, product, role and
scenario forms against their published primitive contracts. Repository scope
also resolves references and checks subtype-dependent references. These singular
forms are distinct from plural view notations.
Field draft and observation validation checks identity, admission and source
evidence; payload text remains opaque. Both explicit notation and the existing
zone: field plus uppercase type envelope are recognised. Supported provenance
forms are captured-source records (captured_by, captured_on, setting) and
versioned imports (source_revision, original_path, source_hash,
captured_on). Other nonempty evidence forms remain unvalidated and produce
NOTATION-SKIP-001; these implementation forms do not redefine the methodology.
Repository scope rejects duplicate Field IDs.
Headerless .history.yaml/.history.yml files receive raw shape, date, duplicate,
value and target-lifecycle checks before any permissive parsing. Capability and
application history attributes are supported. Unknown target schemas, or a
file-only check without target context, remain unvalidated.
Raw archives under codex/**/sources/ are accounted for as exclusions, consistent
with co-located snapshot_file references. Admission there remains an ADMIT-012
error; canon/sources/ and field/sources/ gain no exemption.