Skip to content

Latest commit

 

History

History
1357 lines (1176 loc) · 73.1 KB

File metadata and controls

1357 lines (1176 loc) · 73.1 KB

BPMN 2.0 Validation Rules

Overview

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 scope (file vs repo)

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 id defined in more than one file.
  • Atomicity — an element file carrying an inline relations: section (relations belong in canon/relations/).
  • Referential integrity — a relation endpoint (from/to, or the legacy source/target) that does not resolve to a known element.
  • Policy — an element marked Active/Production (metadata.status) with no metadata.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), plus TSVC-003 (TECHNOLOGY_SERVICE.node → NODE) and INT-002 (INTEGRATION interface endpoints → APPLICATION). process_parent also fires REL-007 (self-reference, error) and REL-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 the views array (not canon) when the document lives under canon/views/** — see below.

Whole-zone enumeration

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.

Strategy-chain semantic rules (GOALS-* / ACT-* / FGCA-*)

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.

Standalone-element envelope rules (GOAL-ELEM-* / ACTION-*)

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.

Doc-level grammar rules (GOALS-* / ACT-* / FGCA-*), via the views sweep

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.

Document sources (.ttrs) — extension, placement, kind (HDR-003 / TTRS-013)

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.

Versioned-attribute sidecar rules (VERSIONED-00x)

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.

Candidate-only fields on admitted canon (ADMIT-009)

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.

Compliance suite (--scope=repo, #518)

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.

Resolved model output (--include-model)

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's layer field, else derived from the canon/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/sourceFile stay a minimal, stable identity projection for consumers that only need those; data is the faithful projection for a consumer that needs a field the engine already parsed but the minimal set doesn't carry (a goal's level/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's type field, omitted when absent), source/target (resolved from/to, or the legacy source/target keys). 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.

Rule Categories

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.)

Rule Severity

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

Structural Rules (RD-096 Foundation)

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.

Start Event Rules (RD-101)

Semantic validation for BPMN 2.0 start event constraints per method/methodology.md Section 7.

SE-001: Process has at least one start event

  • 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"
    }

SE-003: Start event has no incoming flows

  • 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"
    }

SE-004: Start event has exactly one outgoing flow

  • 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"
    }

End Event Rules (RD-102)

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.

EE-001: Process has at least one end event

  • 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"
    }

EE-003: End event has no outgoing flows

  • 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"
    }

EE-004: End event has at least one incoming flow

  • 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"
    }

Rule Development Guide

Writing a New Validation Rule

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
  }
}

Example: Event Type Validation (RD-098)

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
  }
}

Registering a Rule

Add the rule to the global registry in src/validator.ts:

validator.register(rule_XX_NNN)

Testing a Rule

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',
    })
  )
})

Validation Checklist for Rule Authors

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; optional elementId, 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

API Integration

Validation findings are included in HTTP API responses:

POST /api/compile

Response:

{
  "xml": "...",
  "metrics": { ... },
  "validation": {
    "isValid": true,
    "findings": [],
    "summary": {
      "errorCount": 0,
      "warningCount": 0,
      "infoCount": 0
    }
  }
}

CLI Output

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

Known Limitations

  • 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.

Future Work

  • 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

Sequence Flow Rules (RD-099, RD-100)

Semantic validation for BPMN 2.0 sequence flow constraints, including routing restrictions and duplicate detection.

SF-DUP: No duplicate sequence flows (RD-099)

  • 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"
    }

SF-001: Flow endpoints must exist (RD-100)

  • 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 from and to element 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"
    }

SF-005: Condition expressions only on Activity or XOR gateway outgoing flows (RD-104)

  • 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"
    }

SF-006: Default flow marker only on Activity or XOR gateway outgoing flows (RD-104)

  • 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"
    }

SF-007: Flow cannot have both default marker and condition expression (RD-104)

  • 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.default and flow.condition are 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"
    }

Intermediate Event Rules (IE)

Semantic validation for BPMN 2.0 intermediate (catch) events — intermediateMessageEvent, intermediateTimerEvent.

IE-001: Intermediate events have incoming and outgoing flows

  • 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"
    }

Gateway Rules (RD-105)

Semantic validation for BPMN 2.0 gateway element constraints per method/methodology.md Section 7.

GW-XOR-01: XOR gateway cannot have single incoming and single outgoing flow

  • 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)"
    }

GW-AND-04: Parallel gateway split outgoing flows must not have conditions

  • 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.condition set.
  • 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"
    }

Activity Rules (RD-103)

Semantic validation for BPMN 2.0 task (activity) constraints per method/methodology.md Section 7.

ACT-001: Task has incoming and outgoing flows

  • 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"
    }

Connectivity Rules (RD-106)

Semantic validation for process connectivity constraints per method/methodology.md Section 7.

CONN-001: Every element is reachable from a start event AND reaches an end event

  • 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"
    }

CONN-002: Graph is weakly connected (no isolated islands)

  • 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.

CONN-003: Every flow source has a reachable target (transitively covered)

  • Status: Fully covered by CONN-001 + parser validation.
  • Rationale: The parser validates all flow.to references 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 (RD-107 onwards)

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).

AP-FLOAT: Element has no incoming or outgoing flows (RD-107)

  • 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"
    }

AP-NO-DEFAULT: XOR split with all conditional flows but no default (RD-108)

  • 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 >= 0 instead of amount > 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"
    }

AP-IMPLICIT-JOIN: Task has multiple incoming flows without a joining gateway (RD-109)

  • 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"
    }

AP-GW-AS-TASK: Gateway name suggests it might be a task (RD-110)

  • 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."
    }

Configuring rules via .transitrixrc

The canonical config file is .transitrixrc (JSON). Legacy .cervinrc is 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 as AP-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 default
  • AP-NO-DEFAULT: warning, enabled by default
  • AP-IMPLICIT-JOIN: warning, enabled by default
  • AP-GW-AS-TASK: warning, disabled by default — enable with "warn"

Association Rules (P0b)

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.

ASC-001: Associations must connect a data object to an activity

  • Severity: error
  • Description: Each entry in associations must have exactly one dataObject endpoint and one activity endpoint (task, userTask, or serviceTask). Associations between two activities or two data objects are invalid — use sequence flows between activities, and model data dependencies as dataObject ↔ activity associations 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."
    }

ASC-002: Association endpoints must reference existing elements

  • Severity: error
  • Description: Both from and to of 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"
    }

ACTION diagnostic compatibility

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.

Authored numeric risk degrees

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.

Diagnostic conformance and capability history

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.

Standalone primitives, Field evidence and history coverage

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.