All Transitrix notations share the same file-header contract: the same required field, the same reserved field, the same validator rules, and the same extension/content match guarantee. This document defines those shared rules once. Each notation spec links here and lists only its per-notation values (the notation: short name and the file extension).
This document also defines four organisation-level contracts shared across all notations: the zone model (§5), the admission record (§6), the primitive lifecycle (§7), and the versioned-attribute sidecar (§9) — the four shared shapes every organisation artefact may carry. §8 aggregates the validation rules of the compliance domain (REQUIREMENT + ASSERTION) for discoverability — the per-notation specs remain authoritative for the rule definitions themselves. §10 sets the versioning and compatibility policy for the methodology itself — what kind of change each SemVer bump may carry, and what adopters can rely on across releases. §12 defines the extensions bag (extensions: — the open attribute escape hatch on every entity) and §13 the unresolved holding area (canon/unresolved/ — where ingestion parks an object it cannot yet type); together they are the zero-information-loss contract the ingest pipeline relies on. §14 defines the view-config contract — the presentation layer of a view document — and §14.5 the rendered snapshot format — the committed output artefact produced by each CLI Capture run. §15 defines the domain vocabulary separating the project-domain Action from the process-domain Activity.
A change to the rules below applies to all notations simultaneously — they should be edited here, not duplicated into each spec.
Every Transitrix notation file MUST start with a header that declares which notation the file follows. The header is YAML key/value syntax at the top of the file — for every notation in the family, the file itself is YAML and the header is the document's leading keys.
notation: <short-name> # required; this notation's short name
spec_version: "0.1" # optional today; reserved field; will be required when this notation reaches v1.0
# … rest of the document| Field | Required | Type | Semantics |
|---|---|---|---|
notation |
yes | string | Short name of the notation (bpmn, dgca, goals, capability-map, …). Identifies the schema the rest of the document follows. The accepted short names are listed in README.md. |
spec_version |
no, accepted | string | Declared version of the notation spec the document conforms to. Reserved today; will become required when each notation reaches v1.0. The validator accepts but does not enforce it. |
The short name is fixed per notation and matches the per-notation table at the bottom of the spec being read.
Every view notation document MUST declare the following fields at the document root, alongside notation: and spec_version::
notation: <short-name> # §1 — required
spec_version: "0.1" # §1 — optional
name: "Human-readable title" # §1.1 — required
generated_at: "YYYY-MM-DD" # §1.1 — optional; quoted ISO 8601 date per §4
description: "One paragraph." # §1.1 — optional
# … rest of the document| Field | Required | Type | Semantics |
|---|---|---|---|
name |
yes | string | Human-readable document name — displayed in Studio diagram previews and listings. |
generated_at |
no | string | Date the document was generated or last substantively revised — quoted ISO 8601 date per §4. Distinct from the git modification date; records the authoring intent, not the file write. |
description |
no | string | One-paragraph context — what the document covers and why. |
Root placement is mandatory. Placing name: or generated_at: only inside a nested notation object (e.g. nested_blocks.name, process_blueprint.name, view.name) does not satisfy this contract. Renderers and tooling read root-level fields; notation-specific nested objects may carry their own name or date fields for notation-internal purposes, but those are not the document metadata fields defined here.
Every notation's compiler / validator enforces the same four header rules:
| Rule | Severity | Description |
|---|---|---|
HDR-001 |
error | Missing notation field. |
HDR-002 |
error | notation value does not match the short name expected for this notation. The file is probably in the wrong format for its extension. |
HDR-003 |
error | File extension does not match the canonical extension for the notation declared inside the file (extension/content mismatch). Every notation has its own extension: *.dgca.transitrix.yaml for dgca, *.goals.transitrix.yaml for goals, *.activities.transitrix.yaml for activities; for all other notations it is *.<short-name>.transitrix.yaml. |
HDR-004 |
accepted | spec_version is accepted but not enforced until the notation reaches v1.0. |
Additional notation-specific rules (per-field, semantic, structural) live in the respective spec's "Validation rules" section.
Each notation has exactly one canonical file extension: *.<short-name>.transitrix.yaml. The dgca, goals, and activities notations are each distinct and each carry their own extension (*.dgca.transitrix.yaml, *.goals.transitrix.yaml, *.activities.transitrix.yaml respectively), even though they describe related layers of the same strategy-execution family. The notation: header inside the file identifies the specific notation; the extension mirrors it. The validator enforces this per rule HDR-003.
No aliases are accepted: one notation has exactly one extension. The full per-notation mapping lives in README.md.
All date-typed fields across the Transitrix notations MUST be quoted ISO 8601 strings in YYYY-MM-DD form (e.g., "2026-06-01"). Unquoted 2026-06-01 is parsed by YAML 1.1 loaders as a native date type and is not accepted as the canonical form. Quote dates explicitly.
Which fields are date-typed is defined per notation — the shared document-metadata field generated_at: (§1.1), plus fields such as activity start_date / end_date and project.start_date / project.calendar.holidays[], capability assessment_date / target_date, issue created_at / resolved_at / updated_at, and application / product updated_at. The quoting rule above applies to every one of them; specs reference this section rather than restating it.
A modelled organisation accumulates three kinds of knowledge with different trust contracts. Every artefact under an organisation belongs to exactly one zone, declared by a zone: field in its admission record (§6).
| Zone | What it holds | Trust contract |
|---|---|---|
canon |
Validated truth about the organisation — what it officially asserts about itself (its elements and views). | Internally consistent and unique; the authoritative model. |
field |
Raw, unprocessed material — interviews, surveys, observations, drafts. | Contradictions allowed; provenance is the point. Not authoritative. |
codex |
External constraints (laws, regulations) and internal authority documents (policies, standards) — given to the organisation rather than authored by it. | Faithful to an external or issuing source; not edited to fit the model. |
Zones are parallel, not stacked. There is no hierarchy and no numeric ordering between them — canon/, field/, and codex/ sit side by side under an organisation, never as 1_canon/, 2_field/.
Data is not migrated between zones. Each zone has its own admission gate (§6); an artefact admitted to one zone is never moved to another. A Canon record MAY cite the Field material that informed it via derived_from: (§6) — that citation is the link, not a migration. Re-deriving Canon from Field yields a new Canon artefact; the Field artefact stays where it is.
Each zone has an admission gate: before an artefact enters a zone it is validated, transformed into the zone's canonical form, and tagged. Admission leaves a record in the artefact's frontmatter, so the gate that admitted it is auditable.
zone: canon # canon | field | codex — required
admitted_at: "2026-05-27" # quoted ISO 8601 date (§4)
admitted_by: "v.korobeinikov" # person handle or tool ID that ran the gate
gate_checks: # sub-map keyed by check name → outcome
uniqueness: pass
consistency: pass
completeness: pass
derived_from: # optional; typed IDs of the artefacts this one derives from
- INTERVIEW-cfo-onboarding-2026-04-15-1| Field | Required | Type | Semantics |
|---|---|---|---|
zone |
yes | string | The zone this artefact belongs to: canon, field, or codex. |
admitted_at |
yes | string | Date the artefact was admitted to its zone — quoted ISO 8601 per §4. |
admitted_by |
yes | string | The person handle or tool identifier that ran the admission gate. |
gate_checks |
yes | map | Sub-map keyed by check name; each value records the outcome (pass, or a short note). The standard checks per zone are listed below. |
derived_from |
no | list | Typed IDs (per IDS_AND_REFERENCES.md) of the artefacts this one was derived from. This is how a Canon record cites the Field material behind it (§5) — a citation, never a migration. |
source_quality |
field zone: recommended | string | Authored trust in the source of the material, from the closed set authoritative / corroborated / single_source / unverified (§11.2). Part of the field zone's provenance contract (§5). Absent ⇒ downstream confidence scoring (§11) treats the source as unverified. Not meaningful on canon / codex artefacts. |
Standard gate_checks per zone — the minimum each zone's gate asserts:
| Zone | Standard checks | Meaning |
|---|---|---|
canon |
uniqueness, consistency, completeness |
IDs unique within the catalogue; no contradiction with existing canon; required fields present. |
field |
provenance |
The source of the material is recorded — who, when, in what setting, and at what trust (source_quality, §11.2). |
codex |
source_authority |
The issuing or authoritative source is identified and the artefact is faithful to it. |
A zone MAY record additional checks beyond its standard set; codex artefacts additionally carry zone-specific frontmatter defined in the codex notation spec.
The admission record above describes an artefact that has passed its zone gate. Some artefacts are produced by an automated harvest — a scanner / collector that extracts candidate canon from a source (a REQUIREMENT from a regulation, an ASSERTION of impact) — and must not enter admitted canon until a human reviews them. The collector never writes admitted canon directly: it emits proposed drafts plus a review digest, and a human gate admits or rejects each.
A pre-admission state, recorded by admission_state on the admission record, governs this. It is orthogonal to zone: zone names the artefact's target zone (where it lives once admitted), while admission_state records whether it has yet passed that zone's gate.
zone: canon # the TARGET zone — where this artefact lives once admitted
admission_state: proposed # proposed | active | rejected — absent ⇒ active (back-compat)
proposed_at: "2026-06-05" # required when proposed/rejected — when the harvest emitted the draft
proposed_by: "reg-intel-collector" # required when proposed/rejected — the tool / harvest id (never a human)
owner_to_confirm: ROLE-LEGAL-1 # recommended when proposed — the ROLE accountable for the admission decision
gate_checks:
uniqueness: pass # checks the harvest can self-certify mechanically
consistency: pass
completeness: pending_review # human-judgement checks the harvest cannot certify
derived_from:
- REGULATION-GDPR-2016-1
# admitted_at / admitted_by are ABSENT until a human admits (proposed → active)| Field | Required | Type | Semantics |
|---|---|---|---|
admission_state |
no | string | proposed | active | rejected. Absent ⇒ active — human-authored canon is admitted by construction, so existing files need no change (back-compat). |
reviewer_authority |
no | string | ai_reviewed | expert_confirmed — the authority tier of the reviewer who admitted the artefact (tiered approval, §6.2). Absent ⇒ expert_confirmed (back-compat: every artefact admitted before this axis existed is treated as expert-confirmed; no migration). Only meaningful with admission_state: active; both tiers are admitted canon. A tool writes ai_reviewed, a human writes expert_confirmed (ADMIT-007). Orthogonal to admission_state and to the two trust signals (source_quality, extraction_confidence); never folded into the §11.4 confidence formula (§11.4). |
proposed_at |
when proposed / rejected |
string | Date the automated harvest emitted the draft — quoted ISO 8601 (§4). |
proposed_by |
when proposed / rejected |
string | The tool / harvest identifier that emitted the draft. A human never writes proposed. |
owner_to_confirm |
recommended when proposed |
string | ROLE-… ID of the ROLE accountable for reviewing this proposed draft and making the admission decision (proposed → active | rejected). Routes the open item to the designated inbox; absent ⇒ the draft is unrouted (warning ADMIT-008). Not used on active or rejected artefacts — admission is already decided. |
rejected_at |
when rejected |
string | Date a human refused the draft. |
rejected_by |
when rejected |
string | The human reviewer who refused it. |
rejection_reason |
recommended when rejected |
string | Why the draft was refused — kept for audit. |
For admission_state: proposed, the §6 requirement on admitted_at / admitted_by is deferred (they are filled only on admission); proposed_at / proposed_by stand in their place.
The state machine:
(automated harvest emits)
│
▼
proposed ──(human gate: review, complete gate_checks, accept)──▶ active
│
└────────────(human review: refuse)──────────────────────▶ rejected
proposed— written only by an automated harvest. The artefact is shaped as its target canon TYPE and is structurally validated as that TYPE, but it is not admitted canon: it is excluded from the admitted set and from every derived view (below).admitted_at/admitted_byare absent; human-judgementgate_checkscarrypending_review.owner_to_confirm(recommended) routes the open item to the ROLE accountable for the admission decision.active— admitted to canon. A human reviewer runs the canon gate (§6), completes everygate_checksentry topass, setsadmission_state: active, and fillsadmitted_at/admitted_bywith the admission date and their handle. This is the only transition into admitted canon for a harvested draft. (Human-authored canon starts here directly, withadmission_stateabsent.)rejected— a human reviewed the draft and refused it. The file is retained as an auditable decision record (who, when, why) but is never admitted canon and is excluded from derived views. A later harvest that re-finds the same source emits a newproposeddraft;rejectedis terminal and is not reopened.
active and rejected are terminal for this pre-admission machine. Retirement of an already-active element is the separate valid_to lifecycle (§7), not a state here.
Exclusion from derived views and cross-cutting checks. Only active (and absent ⇒ active) artefacts constitute admitted canon. Renderers, views, and cross-cutting validators operate on the admitted set: a proposed or rejected artefact does not appear in any derived view and does not count toward coverage (e.g. REQ-COVERAGE-001). A proposed artefact MAY reference another proposed artefact in the same harvest batch; an active artefact MUST NOT depend on a proposed one — admit the dependency first (ADMIT-005).
Validation rules (apply to every canonical artefact, alongside the header and lifecycle rules):
| Rule | Severity | Description |
|---|---|---|
ADMIT-001 |
error | admission_state is present and not one of proposed / active / rejected. |
ADMIT-002 |
error | admission_state: proposed but admitted_at / admitted_by are present (a proposed draft is not yet admitted), or proposed_at / proposed_by are missing. |
ADMIT-003 |
error | admission_state is active or absent, but admitted_at / admitted_by are missing or any gate_checks entry is not pass. |
ADMIT-004 |
error | admission_state: rejected but rejected_at / rejected_by are missing. |
ADMIT-005 |
warning | An active artefact cross-references a proposed or rejected artefact — admitted canon must not depend on un-admitted drafts. Cross-cutting (requires the full catalogue). |
ADMIT-006 |
error | reviewer_authority is present and not one of ai_reviewed / expert_confirmed (§6.2). |
ADMIT-007 |
error | The admitter does not match the tier: reviewer_authority: ai_reviewed but admitted_by identifies a human, or reviewer_authority: expert_confirmed but admitted_by identifies a tool. A tool writes ai_reviewed; a human writes expert_confirmed (§6.2). |
ADMIT-008 |
warning | admission_state: proposed and owner_to_confirm is absent — the open item has no designated reviewer and will not route to any inbox. |
This lifecycle is what an automated regulatory-intelligence collector (a separate task) depends on: the collector emits proposed drafts plus a review digest, and the human gate admits or rejects. The per-TYPE specs note it where relevant (15-requirement.md, 16-assertion.md).
Admission carries a second, orthogonal axis: who confirmed it. The single human gate (§6.1) records only who admitted; it cannot distinguish a draft an AI reviewer ticked from one a domain expert confirmed, so the only safe default is to hold everything behind the expert gate — which does not scale to a source that yields hundreds of REQUIREMENT / ASSERTION candidates. reviewer_authority grades the authority tier of the reviewer, independent of admission_state (gate progress) and of the two trust signals (source_quality, extraction_confidence — §11). (ADR docs/decisions/2026-06-10-tiered-approval-reviewer-authority.md.)
The tiers are ordinal — ai_reviewed < expert_confirmed — and both are admitted canon (admission_state: active); the axis adds no new exclusion from derived views. Write authority is strict:
ai_reviewed— written only by a tool acting as reviewer (the ingest skill, an automated cross-check). The tool fillsadmitted_bywith its tool id. A tool MAY admit a high-extraction_confidencedraft straight to this tier; it may never writeexpert_confirmed.expert_confirmed— written only by a human reviewer. Absentreviewer_authority⇒expert_confirmed.
This keeps "propose, never write the expert tier": the human gate retains exclusive authority over the top tier, so the core invariant (a tool never mints expert-confirmed canon unreviewed) holds. An ai_reviewed active record:
zone: canon
admission_state: active
reviewer_authority: ai_reviewed
admitted_at: "2026-06-10"
admitted_by: "ingest-reviewer-claude" # a tool id — ADMIT-007
gate_checks:
uniqueness: pass
consistency: pass
completeness: pass
derived_from:
- REGULATION-GDPR-2016-1Cross-tier dependencies — weakest link. An expert_confirmed artefact MAY depend on an ai_reviewed one: the lower tier is canon, so the dependency is allowed and ADMIT-005 is not extended to forbid it. Views surface the weakest-link authority of the dependency chain — the displayed reviewer authority of a chain is the minimum tier over all of its nodes, so a single ai_reviewed node anywhere in the chain makes the whole chain read ai_reviewed. Rendering the weakest-link chain in Studio / DSM is a separate follow-up; this section defines the rule.
The routing that decides which drafts a tool may auto-admit to ai_reviewed (high extraction_confidence) versus send to the expert queue (medium / low) is a skill-level rule, not a CONTRACT one — it lives in the ingest skill (transitrix/skills/ingest/SKILL.md). The CONTRACT defines the tiers; the skill defines the routing.
Transitrix shows the organisation in motion — what existed when, what changed, what's gone. Every canonical element therefore carries a lifecycle in its frontmatter: when it became valid, and when it stopped (or null if still in effect).
valid_from: "2026-05-27" # quoted ISO 8601 date (§4) — when the element took effect
valid_to: null # quoted ISO 8601 date or null — when the element ended (null = still in effect)| Field | Required | Type | Semantics |
|---|---|---|---|
valid_from |
yes | string | Date the element took effect — quoted ISO 8601 per §4. |
valid_to |
yes | string | null | Date the element ceased to be in effect — quoted ISO 8601 per §4, or null. null means the element is currently in effect. |
The lifecycle frontmatter sits alongside the admission record (§6); the two are distinct. Admission records when an artefact entered its zone (a one-time gate event); lifecycle records when the element it represents is in effect (a temporal property of the modelled thing, independent of when it was admitted). An artefact admitted today may legitimately carry valid_from years in the past — the organisation is recording history.
The lifecycle contract applies to every canonical element — each individual primitive the organisation asserts. For element-primitive files (one element per file, under canon/elements/<NN>_<layer>/), the lifecycle fields sit in the file's frontmatter. For view documents that define elements inline (capability-map, FGCA, applications catalogue, …), each inline element entry carries its own valid_from / valid_to. The view document itself does not carry a lifecycle — it is a view, not an element.
Each notation spec lists which of its top-level entries are elements (and therefore lifecycle-bearing) versus document-level metadata (and therefore not). Per-notation specs reference this section rather than restating the rule.
Attributes that change over time within a primitive's lifecycle (a capability's maturity level, an application's vendor) are a separate concern, handled by the versioned-attribute sidecar defined in §9. The primitive's own valid_from / valid_to mark the window the element is in effect; values that move inside that window live in <primitive_id>.history.yaml. A notation spec declares which of its fields are time_varying and therefore sidecar-bound.
Every notation's validator enforces the same four lifecycle rules:
| Rule | Severity | Description |
|---|---|---|
LIFECYCLE-001 |
error | valid_from missing or not a parseable quoted ISO 8601 date. |
LIFECYCLE-002 |
error | valid_to is present, is not null, and is not a parseable quoted ISO 8601 date. |
LIFECYCLE-003 |
error | valid_to is a date earlier than valid_from. |
LIFECYCLE-004 |
warning | A cross-reference resolves to a primitive whose valid_to is earlier than the referring primitive's valid_from — the referenced primitive had already ended before the referrer began (a dangling temporal reference). A per-notation spec MAY downgrade this to info for relation kinds where a stale reference is expected (e.g. an Issue that explicitly references a retired component). |
The lifecycle fields are required on every canonical primitive once a notation spec is updated to reference this section. Existing canonical files in adopter repositories backfill: valid_from = the file's last_updated (or, if absent, a sensible organisation-chosen epoch); valid_to: null. A mechanical sweep — no manual decision per file is needed.
ID-rename migration (two-phase pattern). When an element's canonical ID changes — whether a TYPE-prefix rename (e.g. FACTOR-… → DRIVER-…) or an individual element renumber — update relation files in two phases rather than a single atomic sweep:
- Phase 1 — bridge. Rename the element file and set its
id:to the new ID. Add the old ID toformer_ids:on the element file (ELEMENT_PRIMITIVES.md §3). Existing RELfrom:/to:references that carry the old ID continue to resolve — the resolver falls back toformer_idswhen the literal value does not match any live elementid. Ship this phase immediately; no relation-file changes required. - Phase 2 — sweep. In a dedicated follow-up commit, update all REL files to reference the new ID. Remove the old ID from
former_ids. Track the sweep in the task issue.former_idsis a temporary bridge; entries must not persist after the sweep is complete.
ELEM-FORMER-ID-001 (ELEMENT_PRIMITIVES.md §9) flags any former_id that collides with a live element id or another element's former_ids — such a collision means Phase 2 was not completed cleanly.
- Bitemporality. No separate
transaction_timevsvalid_time. v1 records what is true now about what was true then; back-dating corrections rewrite the file via git, and the git history is the audit trail. - Branching timelines. Alternative futures are the concern of the Scenarios notation (
notations/views/11-scenarios.md), not of the primitive lifecycle. - Sub-day precision. ISO 8601 date precision only; no timestamps, no timezones in canon. "Today" is the date of the query or render.
- First-class time-aware relations. Promoting relations like
parent/applies_to/ activity→goal to first-class lifecycle-bearing files is planned for Wave 3 of the temporal model. In v1 such relations remain inline and timeless on their host primitive.
The compliance domain spans two notations — REQUIREMENT (motivation-layer element, 15-requirement.md) and ASSERTION (canon-zone primitive linking a requirement to a subject, 16-assertion.md). For discoverability, the validation rules for both are aggregated below in a single table. The per-notation specs remain the authoritative source for the rule definitions; this table is an index.
| Rule | Severity | Notation | Short description | Authoritative spec |
|---|---|---|---|---|
REQ-001 |
error | REQUIREMENT | id grammar invalid, or any required field missing |
15-requirement.md §4 |
REQ-002 |
error | REQUIREMENT | derived_from references an ID that does not resolve |
15-requirement.md §4 |
REQ-003 |
error | REQUIREMENT | derived_from ID is not of TYPE LAW / REGULATION / POLICY / INTERNAL_STANDARD |
15-requirement.md §4 |
REQ-COVERAGE-001 |
warning | REQUIREMENT (cross-cutting) | REQUIREMENT has no ASSERTION targeting it — compliance gap | 15-requirement.md §4 |
ASSERT-001 |
error | ASSERTION | a required field is missing, or id grammar invalid |
16-assertion.md §5 |
ASSERT-002 |
error | ASSERTION | about is missing, malformed, or resolves to a non-REQUIREMENT |
16-assertion.md §5 |
ASSERT-003 |
error | ASSERTION | subject does not resolve, or TYPE not in {PRODUCT, PROCESS, CAPABILITY} |
16-assertion.md §5 |
ASSERT-004 |
error | ASSERTION | a realised_via entry does not resolve |
16-assertion.md §5 |
ASSERT-005 |
error | ASSERTION | an evidence[] entry with kind: canonical_ref has a ref that does not resolve |
16-assertion.md §5 |
ASSERT-006 |
error | ASSERTION | status not in the enum (compliant / partial / non_compliant / under_review / n_a) |
16-assertion.md §5 |
ASSERT-007 |
warning | ASSERTION | evidence is empty AND status is compliant or partial — undefended positive claim |
16-assertion.md §5 |
ASSERT-008 |
warning | ASSERTION | next_review_at is set and is in the past — assertion is stale |
16-assertion.md §5 |
ASSERT-009 |
warning | ASSERTION (cross-cutting) | realised_via references a flow step (STEP-…) not yet promoted to a standalone element — promote per ELEMENT_PRIMITIVES.md §7.20 (stage/task-level impact idiom, 16-assertion.md §2.1) |
16-assertion.md §5 |
ASSERT-DEAD-LINK-001 |
warning | ASSERTION (cross-cutting) | subject or realised_via references a primitive whose valid_to is in the past — bound to a currently-retired element |
16-assertion.md §5 |
PROCESS-COVERAGE-001 |
warning | PROCESS (cross-cutting) | PROCESS has no admitted ASSERTION with it as subject — regulatory obligations entirely unmodelled; an n_a assertion counts as coverage |
16-assertion.md §5 |
JURISDICTION-CONSISTENCY-001 |
warning | PROCESS_BLUEPRINT (cross-cutting) | a jurisdiction code in lane_config.compliance_filter.jurisdictions does not match the jurisdiction of any resolved codex source in scope — filter references an unrecognised code |
views/13-process-blueprint.md §6 |
In addition, the shared header rules (HDR-001..004, §2) and primitive-lifecycle rules (LIFECYCLE-001..004, §7.3) apply to REQUIREMENT and ASSERTION files as they do to every other canonical artefact.
The *-COVERAGE-001 / *-DEAD-LINK-001 rules and ASSERT-009 are cross-cutting: their checks span more than one file (a REQUIREMENT's coverage depends on the assertions catalogue; an ASSERTION's dead-link state depends on the lifecycle dates of the primitives it references; ASSERT-009's promotion check depends on the PROCESS flows and the STEP files; PROCESS-COVERAGE-001's check depends on the assertions catalogue; JURISDICTION-CONSISTENCY-001's check depends on the codex catalogue). Notation-local rules check a single file in isolation; cross-cutting rules require the validator to be loaded with the full canon catalogue.
The primitive lifecycle (§7) records when an element is in effect. Some of an element's attributes change within its lifecycle — a capability's maturity level grows over years; a unit's headcount drifts month to month; an application's lifecycle stage moves planned → active → sunset. These time-varying attributes are NOT stored inline on the primitive — inlining loses the history. They live in a sidecar file dedicated to the primitive's history.
For a primitive whose canonical file is <primitive_id>.yaml, time-varying attributes are recorded in a co-located sidecar:
<primitive_id>.yaml # the primitive — stable fields only
<primitive_id>.history.yaml # versioned attributes for the same primitive
The sidecar is a YAML document with this shape:
target: CAPABILITY-V1.2 # required — canonical ID of the primitive this sidecar belongs to
attribute_versions:
maturity_level:
- { valid_from: "2024-01-01", value: 1 }
- { valid_from: "2025-06-01", value: 2 }
- { valid_from: "2026-09-15", value: 3 }
responsible_role:
- { valid_from: "2024-01-01", value: ROLE-OPS-1 }
- { valid_from: "2026-04-01", value: null } # gap marker — attribute unset from 2026-04-01 until next entry
- { valid_from: "2026-07-01", value: ROLE-OPS-2 }| Field | Required | Type | Semantics |
|---|---|---|---|
target |
yes | string | Canonical ID of the primitive this sidecar versions. MUST resolve to an admitted primitive in canon (VERSIONED-001). |
attribute_versions |
yes | map | Sub-map keyed by attribute name; each value is an ordered list of version entries. An attribute name MUST match a time_varying field declared in the primitive's notation spec (see §9.4). |
attribute_versions.<name>[].valid_from |
yes | string | Quoted ISO 8601 date per §4 — when this attribute value took effect. Within an attribute's array, every valid_from MUST be unique (VERSIONED-002); the array SHOULD be sorted ascending (VERSIONED-003). |
attribute_versions.<name>[].value |
yes | scalar | null | The value at this date. null is a gap marker — the attribute is unset from this date until the next entry's valid_from. |
The sidecar does not carry an admission record of its own — it follows its target primitive. It does not carry its own valid_from / valid_to either — its temporal window is governed by target.valid_from and target.valid_to.
For an attribute <name> on the primitive at the time of a query:
- Filter
attribute_versions.<name>[]to entries wherevalid_from <= today(or the query date). - Pick the entry with the largest
valid_from. - If that entry's
valueisnull, the attribute is currently unset; otherwise it is the entry'svalue. - If no entries satisfy
valid_from <= today, the attribute has not yet taken its first value — treat as unset.
This rule means a gap marker (value: null row) makes the attribute currently unset until a later non-null entry's valid_from is reached.
| Rule | Severity | Description |
|---|---|---|
VERSIONED-001 |
error | Sidecar target does not resolve to an admitted primitive in canon. |
VERSIONED-002 |
error | Two or more entries within one attribute's array carry the same valid_from. The current-value resolution rule (§9.2) is ambiguous in this case. |
VERSIONED-003 |
warning | An attribute's array is not sorted by valid_from ascending. Resolution (§9.2) does not require sortedness, but the convention does — the validator MAY auto-sort and emit this warning. |
VERSIONED-004 |
error | A field declared time_varying in its notation spec is present inline on the primitive (it MUST be in the sidecar instead). The primitive may keep the field name reserved for documentation but MUST NOT carry an inline value. |
VERSIONED-005 |
error | A version entry's valid_from falls outside [target.valid_from, target.valid_to]. A versioned attribute cannot take a value before the primitive existed or after it retired. |
The shared header (HDR-001..004, §2) and primitive-lifecycle (LIFECYCLE-001..004, §7.3) rules do not apply to sidecar files — sidecars are not notation documents and carry no notation: header. Their structural correctness is governed entirely by VERSIONED-001..005.
A notation spec MAY declare specific attributes as time_varying. v1 candidate attributes (each landed in a subsequent Wave 2 PR per notation family):
| Notation | Candidate time_varying attributes |
|---|---|
| Capability map (05-capability-map.md) | maturity_level (current/target), responsible_role, target_date |
| Applications catalogue (10-applications.md) | lifecycle_stage (planned / active / sunset), responsible_unit, vendor (when an organisation switches vendors mid-life) |
| Process map (06-process-map.md) | maturity |
| Organisational unit (future) | headcount, head_role |
Each notation's "Element lifecycle" or "Fields" section will, in a follow-up PR, mark its time_varying attributes and remove inline syntax for them. Adopters with existing inline values migrate by moving the value into a single-entry sidecar with valid_from set to the primitive's valid_from.
- Sub-day precision. Same as §7.5 — ISO 8601 date only; no timestamps.
- Versioning relations, not attributes. The sidecar is a shape for attributes (scalar fields of an element). Versioning relations (
parent, cross-references) is the concern of Wave 3 — first-class time-aware relation files — not Wave 2. - Versioning the lifecycle itself.
valid_from/valid_toon the primitive are not versionable. To change a primitive's lifecycle, rewrite the file via git; the git history is the audit trail. - Auto-derived rollups. Cross-attribute computations (e.g. "average maturity over Q3 2026") are query-time concerns of the renderer / DSM, not of the sidecar schema.
§9.1–§9.5 define the versioned-attribute sidecar (<id>.history.yaml): a git-tracked record of how an element's business attributes changed over time — maturity, vendor, owning role — each a value with a valid_from. It is part of the audit trail of canon; a human authors it.
A distinct need arises when an element drives an automated operating activity and accrues runtime operating state — machine-written telemetry about that activity, not a versioned business attribute. The motivating case is a REGISTRY element (ELEMENT_PRIMITIVES.md §7.19): a regulatory-source watch-list whose collector records, per row, when each source was last scanned, when it is next due, whether a change was detected, whether human review is pending, and the latest captured snapshot. This is state, not config, and must not churn the source-of-truth element on every scan.
Such state lives in an operating-state sidecar, co-located with its target element but kept distinct from the versioned-attribute sidecar:
<id>.yaml # the element — authored configuration only
<id>.history.yaml # versioned business attributes (§9.1) — human-authored, canon audit trail
<id>.runstate.yaml # operating state — machine-written runtime telemetry, NOT canon
| Aspect | Versioned-attribute sidecar (.history.yaml) |
Operating-state sidecar (.runstate.yaml) |
|---|---|---|
| Holds | business attributes that changed over the element's life | runtime telemetry of an automated activity the element drives |
| Author | human (admission-gated edits) | machine (the collector / runner) |
| Shape | target + attribute_versions keyed by attribute, each a valid_from-stamped value list (§9.1) |
target + rows/state keyed per the driving element's schema; current-value, not full history |
| Zone | canon-adjacent — part of the audit trail, git-tracked | not canon — carries no admission record; it is regenerable runtime data |
| Validation | VERSIONED-001..005 (§9.3) |
governed by the driving element's spec, not by VERSIONED-* |
Both share the §9.1 sidecar principles — co-located with the target, no admission record of its own, temporal window governed by the target's lifecycle — but an operating-state sidecar is not a versioned-attribute store and is not canon: deleting it loses no authored knowledge, because a re-run regenerates it. The concrete runstate.yaml shape for the regulatory-source registry is defined in ELEMENT_PRIMITIVES.md §7.19; this section fixes only the config/state boundary and the naming convention.
The methodology evolves. Each release changes the contract this document defines, the per-notation specs, or both. Adopters need to know what kind of change a release brings — does it break their existing files, or can they upgrade transparently? This section defines the compatibility policy.
Two version slots exist:
| Slot | Lives in | Records |
|---|---|---|
transitrix.yaml methodology_version |
adopter repository root (see MANIFEST.md) |
The methodology release the whole repo conforms to. Single source of truth for the repo. |
spec_version on each notation file (§1) |
every notation file's header | The notation-spec version the individual file declares. Informational; the manifest decides what version the repo is on. |
Mixed-version repositories are not supported in v1. Every artefact in an adopter repo conforms to the methodology_version declared in transitrix.yaml. No per-folder override; no per-notation override.
Methodology releases use SemVer (MAJOR.MINOR.PATCH) with the semantics below. Each kind of release commits to a different compatibility promise.
| Bump | Kinds of change | Adopter action |
|---|---|---|
MAJOR |
Breaking schema change: renamed field, removed field, new required field, changed validation severity (warning → error), changed enum membership in a closed enum, etc. | Migration required. Adopter follows the migration recipe shipped with the release (defined in a subsequent epic) and updates methodology_version in transitrix.yaml. |
MINOR |
Additive only: new optional field, new notation, new validation code at info / warning, new TYPE in the registry, new section in a spec. Existing files validate cleanly against the new release. |
None required. Adopter MAY adopt new fields when convenient. May update methodology_version in transitrix.yaml to make the upgrade explicit. |
PATCH |
Clarifications, doc fixes, example fixes, no schema change. | None. |
The methodology is pre-1.0. Until the methodology reaches
v1.0.0,MINORbumps may carry breaking changes — standard SemVer pre-1.0 rules apply. Adopters pinning a pre-1.0 version with a caret range (^0.4.x) may be broken by a subsequent0.5.0. Pin exactly (0.4.2) in production adopter repos until the 1.0 cut.
Once the methodology hits v1.0.0, MINOR bumps will be additive only — the policy in §10.2 holds without the pre-1.0 exception.
A released version of the methodology, once tagged, is immutable. Subsequent fixes to that version branch happen as a new PATCH bump; the old tag is not retroactively edited.
A MINOR or PATCH release (post-1.0) MUST NOT break any adopter repo that was valid against the previous release of the same MAJOR line. The validator's error-level rules added in a MINOR or PATCH release apply only to files authored against that release or later, not to files already in adopters' canon.
A MAJOR release SHOULD ship with a migration recipe under migrations/<from>-to-<to>/ — format defined below.
migrations/<from>-to-<to>/
├── README.md # what changed, why, and manual step-by-step instructions
├── codemod.mjs # idempotent automated transform
├── validate.mjs # post-migration check — exits 0 on a clean repo
└── fixtures/
├── before/ # minimal adopter-repo fragment in <from> form
└── after/ # the same fragment after running codemod.mjs
- What changed and why (the schema delta).
- A table of old form → new form for each transformed element.
- Numbered manual steps with
before/aftersnippets for adopters who prefer manual edits. - How to run the codemod and the post-migration validator.
- Pure Node.js ≥ 20 — no native add-ons, no external npm dependencies. Invoked as
node codemod.mjs [--dry-run] [target-dir]; defaulttarget-diriscwd. - Idempotent — re-running on an already-migrated repo is a no-op: no diff produced, exit 0.
- Line-based — text transforms applied line by line, not a YAML parse/serialise roundtrip, so comments, key order, and formatting on untouched lines are preserved.
--dry-run— prints what would change without writing any files.- Diff-style summary — prints each changed file with a count of field-level changes; ends with a totals line (files scanned, files changed).
- Exit codes —
0on a clean run or no-op;1on a detected unsafe ambiguity that requires manual intervention;2on a script-internal error (missing directory, unreadable file).
Checks that the structural changes targeted by the codemod have been applied (e.g. no notation: factor files remain after the 0.6 → 0.7 recipe). Exits 0 if the repo is clean; exits 1 with a list of offending files if not. Does not re-run the codemod — validates only.
fixtures/before/ is a minimal fragment of an adopter repo in the <from> form — just enough files to exercise every transform in codemod.mjs. fixtures/after/ is the expected output after running the codemod against before/. CI MAY assert that running codemod.mjs fixtures/before/ produces a result equal to fixtures/after/ to prevent recipe rot over time.
Worked examples: migrations/0.5-to-0.6/ and migrations/0.6-to-0.7/.
- Migration CLI (
transitrix migrate). Phase 3 — lives in Transitrix Studio, not in this repo. - The 1.0 cut decision. Phase 4 — gated on the in-flight schema epics landing.
- Per-notation versioning.
spec_versionon individual files is informational; onlymethodology_versionintransitrix.yamldrives compatibility decisions. - Migration for adopter repositories of non-methodology versions (DSM, Studio, CLI). Those have their own SemVer policies.
Canon is the authoritative model, but not every canonical statement is equally well-sourced, and confidence in a statement ages — a fact last reaffirmed two years ago is less certain than one confirmed last month, even when both are still valid. This section defines how Transitrix records the trust earned at data entry, how it decays that trust as a statement goes unreaffirmed, and how it surfaces a composite for a rendered view.
Confidence is metadata about certainty, not a contradiction flag. A low-confidence canonical element is still canon — internally consistent and authoritative per §5. Confidence never gates admission and never mutates canon; it is computed, reported, and displayed.
| Signal | Origin | Mutability | Lives in |
|---|---|---|---|
| source trust | Authored at entry — a judgement about who or what the material came from. | Fixed once recorded; a source does not become more or less trustworthy with the passage of time. | source_quality on a field artefact's admission record (§6). |
| freshness | Derived — a function of how long ago the canonical statement was last reaffirmed. | Changes every day; recomputed at query / render time. | Not stored. Computed from the canon element's admitted_at (§6). |
The two are deliberately separate. Collapsing them into one decaying number would erase the authored signal. A statement's overall confidence combines them (§11.4).
source_quality is a closed ordinal set. The numeric weight is used only for the arithmetic in §11.4–11.6; authors record the label, never the number.
| Label | Weight | Meaning |
|---|---|---|
authoritative |
1.0 | Primary source of record: the organisation's system of record, a signed document, or the accountable owner stating it directly. |
corroborated |
0.8 | Confirmed by triangulation across more than one independent field source. |
single_source |
0.5 | One uncorroborated informant or observation. |
unverified |
0.25 | Draft, assumption, inference, or hearsay. The default when source_quality is absent. |
Freshness anchors on the canon element's admitted_at (§6) — the date the admission gate last ran for it. Re-running the gate on an unchanged element ("still true as of today") is a reaffirmation: it bumps admitted_at and resets freshness. This is the maintenance action that cures staleness; canon content is not edited to refresh a score.
age_days = today − admitted_at
freshness = 1.0 if age_days ≤ fresh_days
= floor if age_days ≥ stale_days
= 1.0 − (1.0 − floor) · (age_days − fresh_days) / (stale_days − fresh_days) otherwise
Freshness never reaches zero — old canon is less certain, not worthless; it bottoms out at floor. The three parameters are configured per element TYPE, because different facts age at different rates (an organisation's capability map ages over years; a price list over weeks). They live in the adopter manifest (transitrix.yaml) under confidence_decay (MANIFEST.md §2); the defaults apply to any TYPE not overridden under by_type:
confidence_decay:
defaults: { fresh_days: 180, stale_days: 730, floor: 0.3 }
by_type:
CAPABILITY: { fresh_days: 365, stale_days: 1825 }
APPLICATION: { fresh_days: 180, stale_days: 730 }For one canonical element:
source_trust(element) = max weight over the source_quality of every field artefact
the element cites via derived_from (§6)
confidence(element) = source_trust(element) · freshness(element)
Source trust takes the best available source (max): an element corroborated by an authoritative source is not dragged down by an additional weaker one — extra weak sources add nothing, they do not subtract. Multiplying by freshness then ages that trust: an authoritative-but-long-unreaffirmed element scores 1.0 · floor, while a single_source-but-fresh element scores 0.5 · 1.0.
reviewer_authority is not folded into this formula. The reviewer-authority tier (§6.2 — ai_reviewed vs expert_confirmed) is a property of the review, not of the statement; source trust and freshness are properties of the statement. It is surfaced as a qualitative label alongside the numeric confidence band, never multiplied into it — the same separation §11.1 keeps between the two existing signals. An adopter reads "confidence 0.5, ai_reviewed", not a single blended number.
derived_from remains optional (§6) — requiring it would break existing canon and is not in scope here. But an element with no resolvable derived_from has no authored trust to draw on. For confidence scoring it is treated as unverified (0.25) and counted separately so the gap is visible rather than hidden. This biases toward filling provenance without making it a hard gate.
A view renders a set of canonical elements. Its composite confidence is computed at render time and is not stored — views carry no lifecycle (§7.1) and no confidence state of their own. Three numbers are surfaced alongside the view's formation date:
| Component | Definition |
|---|---|
| weakest link | min of confidence(element) over the rendered elements — a view is only as trustworthy as its weakest element. The headline figure. |
| mean | arithmetic mean of confidence(element) over the rendered elements (equal weight per element in v1). |
| coverage | fraction of rendered elements with a resolvable derived_from. |
For display, a numeric confidence maps to a band: A ≥ 0.8, B ≥ 0.6, C ≥ 0.4, D < 0.4. The headline band is the band of the weakest link. Example render header:
Data confidence (as of 2026-06-05): B (weakest link) · 0.71 mean · 92% sourced
A scheduled validator pass computes freshness across canon and reports — it never writes. It flags every canonical element whose age_days ≥ stale_days for its TYPE (i.e. freshness has bottomed out at floor), so they can be reaffirmed. The cure is re-admission (§11.3), not an edit.
| Rule | Severity | Description |
|---|---|---|
FRESHNESS-001 |
warning | A canonical element's age_days (today − admitted_at) is ≥ stale_days for its TYPE. The element is stale and should be reaffirmed. Advisory only — never blocks, never mutates canon. |
FRESHNESS-001 is cross-cutting in the §8 sense: it needs the element's admission date and the active confidence_decay config, not just the file in isolation.
- Persisting the confidence trajectory. Confidence is recomputed on read. If an organisation later needs the history of a confidence value, the versioned-attribute sidecar (§9) is the mechanism — not added here.
source_qualityas a first-class entity. Today it is a label on a field artefact's provenance, not a referenced source record. Promoting sources to entities is a later concern.- Automatic reaffirmation. Bumping
admitted_atis a deliberate human / tool gate action; nothing reaffirms canon on its own. - Importance-weighting the mean. v1 weights every rendered element equally; weighting by element importance is deferred.
Real source material carries fields that map to no defined schema field of the entity being ingested. Dropping them loses information; inventing ad-hoc top-level keys pollutes the schema and trips the validator. Every entity type therefore carries one reserved open key-value bag, extensions:, that holds source-derived fields the schema does not define. It is the zero-information-loss escape hatch for a known entity; the parallel escape hatch for an unknown whole object is the unresolved holding area (§13).
type: PRODUCT
id: PROD-001
name: Widget Pro
# … standard schema fields …
extensions:
materials:
- "Steel 316L"
- "Rubber gasket B12"
source_table: product_equipment_matrix
source_field: materialsextensions:is optional on every entity type and, when present, is a map (an open key-value bag). Its keys are free-form; its values may be any YAML value (scalar, list, map).- The validator passes
extensions:through untouched — it never raises an unknown-field error forextensions:itself or for any key nested under it, and it does not constrain their shape (EXT-001). extensions:is for fields the schema does not define. A field the entity's notation already defines belongs in its defined place, never relocated intoextensions:to dodge a schema rule (EXT-002).extensions:carries no admission, lifecycle, or confidence semantics of its own — it rides on its host entity and shares the host's admission record (§6) and lifecycle (§7).
| Rule | Severity | Description |
|---|---|---|
EXT-001 |
accepted | extensions: and every key nested under it are accepted without schema validation — the validator never raises an unknown-field error for them. The pass-through guarantee. |
EXT-002 |
warning | A key under extensions: collides with a field the entity's notation already defines — the value probably belongs in its defined place, not in the open bag. |
extensions: holds schema-undefined fields of a known entity, stored inline on that entity. It is distinct from:
- the versioned-attribute sidecar (§9) — for defined fields whose value changes over time;
- the unresolved holding area (§13) — for a whole object whose TYPE is unknown, not merely an extra field on a known one.
The full routing decision (known object / unknown field / unknown object / law-or-standard) lives in the ingest skill (transitrix/skills/ingest/SKILL.md); §13.3 restates its canon-side summary.
Ingestion sometimes surfaces a standalone object whose TYPE matches no known entity — not a PRODUCT, ACTIVITY, CHANGE, …, and not merely an attribute of a known object (that would be an extensions: key, §12). Discarding it loses information; admitting it under some guessed TYPE corrupts canon. Such objects land in a reserved holding area, canon/unresolved/, with their ingestion provenance preserved, pending human resolution.
# canon/unresolved/UNRES-001.yaml
ingest_status: unresolved
ingest_source: product_equipment_matrix.xlsx
ingest_field: materials
ingest_date: "2026-06-10" # quoted ISO 8601 per §4
related_to:
- PROD-001
data:
- "Steel 316L"
- "Rubber gasket B12"An unresolved object is not low-trust raw material. Its content may be entirely accurate — drawn from an authoritative system of record, validated, and canonical in every respect except that its TYPE is not yet resolved. "Unresolved" names exactly one gap: the object's TYPE is unknown. It says nothing about whether the content is true.
Type resolution is therefore a second axis, orthogonal to admission (§6) — exactly as reviewer authority (§6.2) is orthogonal to admission state (§6.1):
| Axis | States | Question |
|---|---|---|
| admission (§6) | proposed → active |
Is the content validated and admitted? |
| type resolution (§13) | unresolved → typed |
Is the object's TYPE known? |
The two move independently. An entry in canon/unresolved/ MAY carry a full admission record — admitted_by, gate_checks, source_quality (§11.2), even a lifecycle (§7) — i.e. it can be admitted-but-untyped: a human has confirmed the content is accurate, but no TYPE has been assigned. It MAY equally be proposed-but-untyped (freshly ingested, content not yet reviewed). Both live here; the admission record, when present, is honoured exactly as it is on typed canon.
Why it is segregated from typed canon — TYPE, not trust. The canon machinery is TYPE-keyed: a canonical id is <TYPE>-<INTEGER> (IDS_AND_REFERENCES.md §1), placement is per-TYPE (ELEMENT_PRIMITIVES.md §4), and relations (§17) and derived views dispatch on TYPE. An object with no resolved TYPE cannot take a canonical id, cannot be placed in a per-TYPE folder, and cannot be rendered by a TYPE-keyed view. It is held in canon/unresolved/ — fully part of canon's knowledge, but outside the typed machinery — until its TYPE is resolved (§13.3), at which point it earns a <TYPE>-N id and moves to its per-TYPE folder.
Exclusion is from typed derived views, not from canon. An unresolved entry does not render in a TYPE-keyed view and is not counted by TYPE-scoped coverage (e.g. REQ-COVERAGE-001) — that machinery operates on a resolved TYPE the entry does not yet have. Its content is still authoritative canon; it is simply invisible to anything that needs a TYPE. Every tool that walks typed canon (the canon index, coverage, placement check, renderers) MUST therefore skip canon/unresolved/ so an untyped entry is never mistaken for a typed element.
It lives in shared, committed canon — never in _intake/. Because an unresolved object carries real model knowledge, it cannot sit in the per-user, private _intake/ workspace, whose contents are not shared with other modellers. It is committed to canon/unresolved/ so the whole team sees and can resolve it. This is the opposite of a candidate, which is a pre-model extraction proposal that stages privately in _intake/processing/ until a human admits it.
| Field | Required | Type | Semantics |
|---|---|---|---|
ingest_status |
yes | string | Always unresolved for an entry in this folder — the marker of the type-resolution axis (§13.1), independent of admission. It records that the TYPE is unknown, not that the content is untrusted. |
ingest_source |
yes | string | The source the object came from (file name or field-artefact id). |
ingest_field |
yes | string | The source field / column / path the object was extracted from. |
ingest_date |
yes | string | Quoted ISO 8601 date (§4) the object was ingested. |
related_to |
recommended | list | Typed IDs of known canon objects this unresolved object appears related to (e.g. the PRODUCT a materials list hangs off). |
data |
yes | any | The extracted payload, preserved verbatim for the reviewer. |
An unresolved entry MAY also carry any field a typed canon element would — an admission record (§6: admitted_by, gate_checks, admission_state), a source_quality (§11.2), and a lifecycle (§7) — when it is admitted-but-untyped (§13.1). Those fields keep their normal meaning; the entry simply has no resolved TYPE and therefore no <TYPE>-N id. It does not carry a notation: header (it is not a notation document) and is not given a typed canonical id until it is resolved (§13.3).
Human review resolves each entry to exactly one outcome. The full ingestion-routing matrix:
| Situation | Action |
|---|---|
| Known object, unknown fields | extensions: on the object (§12) |
| Unknown fields that are clearly attributes of a known object | extensions: on the parent (§12) |
| Object is a law / rule / standard | codex zone (§5) — not canon/unresolved/ |
| Standalone object, unknown TYPE | canon/unresolved/ (§13) |
| Same unknown TYPE recurs across ingestions | Propose a new entity TYPE in the methodology (a proposal, not an in-repo resolution) |
Resolving one canon/unresolved/ entry means exactly one of:
- promote — the object is a real entity of a (possibly new) TYPE; admit it through the normal admission gate (§6) and delete the unresolved entry;
- fold — the object is actually an attribute of a known entity; move it into that entity's
extensions:(§12) and delete the unresolved entry; - discard — the object carries no modelling value; delete it.
codex is for laws, internal rules, and standards only (§5) — never a destination for a generic untyped ingested object.
| Rule | Severity | Description |
|---|---|---|
UNRES-001 |
error | A file under canon/unresolved/ is missing a required field (ingest_status, ingest_source, ingest_field, ingest_date, or data). |
UNRES-002 |
error | A file under canon/unresolved/ carries a TYPE-resolved canonical id (<TYPE>-<INTEGER> whose TYPE is registered, IDS_AND_REFERENCES.md §3.1) — its TYPE is resolved, so it must move to its per-TYPE canon folder (§13.3), not linger in the holding area. (An admission record is not an error here — an entry may be admitted-but-untyped, §13.1.) |
UNRES-003 |
warning | A related_to entry does not resolve to a known canon object. Cross-cutting (requires the full catalogue). |
UNRES-004 |
error | A typed canon walker (canon index, coverage, placement, renderer) counts a canon/unresolved/ entry as a typed element — the holding area MUST be skipped by TYPE-keyed machinery (§13.1). A tooling rule, enforced by the validator's catalogue load. |
The shared header rules (HDR-001..004, §2) do not apply to canon/unresolved/ files — an unresolved entry has no resolved TYPE and therefore no notation: header, regardless of its admission state.
ELEMENT_PRIMITIVES.md §1.1 defines the reconstruction invariant: a view is render(Elements + Relations, view_config) → diagram. The elements and relations in canon/elements/** and canon/relations/** are the complete, sufficient source of truth for the organisation's behaviour; a view is a projection over them. This section formalises the view_config side of that contract — what it is, what it contains, where it lives, and how per-view specs declare their defaults.
A view_config is the presentation layer of a view document: the configuration that determines which elements and relations appear in a rendered view and how they are arranged. It is the parameters of the render function — not the source data, not canonical content.
Belongs in view_config:
| Category | Examples |
|---|---|
| Selection | which goals to include — by id, tag, type, or all |
| Filter | restrict the element set — by status, layer, zone, valid_at date, jurisdiction |
| Grouping | cluster elements — by layer, domain, type, custom key |
| Ordering | sort criteria within groups |
| Display options | depth limit, collapsed nodes, label format, column visibility, orientation |
Does NOT belong in view_config (these live in canon/elements/** and canon/relations/**):
- Canonical element data — names, descriptions, per-TYPE fields
- Lifecycle dates (
valid_from/valid_to) - Admission records
- Any fact about how the organisation works or what it has decided
Corollary. Deleting canon/views/** entirely loses no model knowledge — views regenerate from elements + view_config files. A view carries no non-derivable information beyond its configuration (ELEMENT_PRIMITIVES.md §1.1).
view_config is a top-level key in a view document (*.<short-name>.transitrix.yaml). It is separate from the file header fields (notation:, spec_version:) and from the view identity block (view.id, view.name, methodology_version). No view_config field belongs in the header; no header field belongs in view_config.
Structural layout of a view document:
notation: dgca # §1 — required header
spec_version: "0.1" # §1 — optional header
methodology_version: "0.7.0" # manifest-pinned methodology version
name: "Retail strategy chain" # §1.1 — required document name
view: # view identity block — id only; name lives at root per §1.1
id: DGCA-RETAIL-1
view_config: # presentation layer — defined here (§14)
goals:
filter: all # selection: include every GOAL in canon
display:
depth: 3 # display option: max depth
collapsed: [] # display option: no collapsed nodesEach per-view notation spec (notations/views/) defines the full set of valid view_config keys for that notation. Only keys defined by the spec are valid; unknown keys are an error (VC-001).
Every view spec MUST declare explicit defaults for every optional view_config field. The defaults block in each view spec serves two purposes:
-
Zero-config renders. A view document that carries only the required envelope (
notation:,view.id,view.name,methodology_version) renders deterministically — each omitted field falls back to its spec default. There is no implicit "show everything" that varies by tool version. -
Skill transparency (ties RPT-1). When the CLI or report skill materialises a minimal view-config in response to a free-text request, it states back exactly which defaults were applied ("full goals set, depth unlimited, no filters"). The defaults block is the source of that statement; the skill reads spec defaults, never invents them.
The defaults block lives in the view spec under a ### view_config defaults (or equivalent) heading. It is a commented YAML block listing each optional key with its default value and a short inline comment explaining the default:
# Canonical defaults — spec authority
# A view_config that omits any of these falls back to the value shown.
view_config:
goals:
filter: all # include every active GOAL in canon
factors:
surface: derived # derive from the included goal set via goal.factors
display:
depth: null # unlimited depth
collapsed: [] # no collapsed nodesEach per-view migration (VP-3+) adds this defaults block to its spec.
| Rule | Severity | Description |
|---|---|---|
VC-001 |
error | A view_config key is not declared in the view spec for this notation — unknown keys are not accepted. |
VC-002 |
error | A view_config entry (e.g. a filter or selection) references an element or relation ID that does not resolve in canon. The view_config is stale and must be updated. |
VC-003 |
warning | A view spec has no explicit defaults block (§14.3). Zero-config renders are undefined for this notation; the CLI/skill cannot state assumptions. |
VC-002 is cross-cutting — it requires the full canon catalogue to resolve. It is reported at render time, not at file-lint time.
A view_config defines what to render (§14.1). A rendered snapshot is the committed output of that render — a point-in-time record of which elements the view projected and their key display values, written by the CLI and checked into the adopter repository. It makes the captured state visible in git without re-running the CLI.
Relation to canon. A snapshot is derived, regenerable output — not canon. It carries no admission record (§6), no lifecycle (§7), and no confidence state (§11). Deleting a snapshots/ folder loses no model knowledge; re-running transitrix capture regenerates it. The snapshot's authoritativeness derives from the canon it was rendered from, not from the snapshot file itself.
Location. Snapshots live in a snapshots/ subdirectory alongside the view document they capture, within the notation's view folder:
views/
<notation>/
<view-file>.<notation>.transitrix.yaml # the authored view document (unchanged by capture)
snapshots/
2026-06-20T143000Z.yaml # one file per CLI Capture run
2026-06-15T091200Z.yaml
File naming. Each snapshot is named with a compact ISO 8601 UTC timestamp: YYYY-MM-DDTHHMMSSZ.yaml — the date portion uses the standard hyphen-separated form; the time portion omits colons so the name is valid on all operating systems (including Windows). The CLI sets the timestamp at the moment of writing.
- Example: a capture at 14:30:00 UTC on 2026-06-20 produces
2026-06-20T143000Z.yaml. - Files sort alphabetically in chronological order; the most recent snapshot is always last.
- Sub-day precision is deliberate: the accumulation rule (below) requires each Capture to produce a distinct file, even when the CLI runs multiple times on the same calendar day.
Required fields — shared envelope. Every snapshot file MUST carry the following fields, regardless of notation:
view_id: DGCA-RETAIL-1 # canonical ID of the view being captured
generated_at: "2026-06-20T14:30:00Z" # ISO-8601 UTC timestamp — matches the file name
methodology_version: "0.7.0" # methodology version in use at generation time
# …notation-specific element list follows (format defined per notation spec)…| Field | Required | Type | Semantics |
|---|---|---|---|
view_id |
yes | string | Canonical ID of the view.id in the view document being captured. |
generated_at |
yes | string | ISO-8601 UTC timestamp of when the CLI wrote this snapshot. Must match the timestamp encoded in the file name. |
methodology_version |
yes | string | Methodology version in effect when the CLI ran — allows staleness detection when the spec evolves. |
Notation-specific content. Beyond the shared envelope, each view notation spec defines which element and relation fields to denormalize into the snapshot. Each per-notation spec MUST define, under a ### Snapshot content heading, at minimum:
- the canonical element
id— traceable back tocanon/elements/ - at least one human-readable key field (e.g.
name,label) — so the snapshot is readable without CLI tooling or canon access
Notation-specific snapshot content definitions are added per notation in subsequent VP-series passes; this section fixes the shared envelope and the conventions that all notation snapshot definitions must follow.
Authoring rules.
- CLI-only writes. Snapshots are written exclusively by
transitrix capture(or the equivalent Studio action). Hand-editing a snapshot file is not accepted. - Read-only after generation. Snapshot files MUST NOT be modified after the CLI writes them. The git history of the
snapshots/folder is the audit trail. - Accumulation. Each
transitrix capturerun creates a new timestamped file. Earlier snapshots are never overwritten or deleted by the CLI. Pruning old snapshots is a manual housekeeping decision by the adopter. - View document unchanged. Capturing a snapshot does not modify the
*.view.yamldocument or any canon element.
Validation rules.
| Rule | Severity | Description |
|---|---|---|
SNAP-001 |
error | A file in a snapshots/ directory is missing a required envelope field (view_id, generated_at, or methodology_version). |
SNAP-002 |
error | view_id does not resolve to a view.id declared in any view document in the same notation folder. |
SNAP-003 |
error | File name does not conform to YYYY-MM-DDTHHMMSSZ.yaml (compact ISO 8601 UTC timestamp, colons omitted, Z suffix required). |
SNAP-004 |
warning | generated_at does not match the timestamp encoded in the file name — the file may have been renamed or copied outside the CLI. |
SNAP-005 |
warning | An element id in the snapshot does not resolve in the current canon — the snapshot is stale relative to the model. Re-run transitrix capture to refresh. Advisory only; never blocks. |
SNAP-002 and SNAP-005 are cross-cutting (require the full canon catalogue and the view document set). SNAP-001, SNAP-003, and SNAP-004 are per-file checks.
Two terms in the methodology carry closely related names and must not be conflated.
| Term | Domain | Definition | Where used |
|---|---|---|---|
| Action | Project domain | A bounded, goal-directed unit of transformation work the organisation undertakes — an Initiative, Programme, Project, or Task (ArchiMate Work Package). Temporary: it starts, delivers, and ends. Modelled as the ACTION element TYPE. |
ACTION elements (canon/elements/05_implementation/actions/); Action schedule (*.action.transitrix.yaml); Actions tree (*.actions-tree.transitrix.yaml); Action Card (*.action-card.transitrix.yaml); DGCA fourth column. |
| Activity | Process domain | A single step in a recurring business process — an operational task that repeats as part of normal operations (BPMN Task / ArchiMate Business Process step). Ongoing: it runs continuously as the business operates, not as a one-off transformation event. Modelled as a node inside a PROCESS element's flow. |
BPMN (*.bpmn.transitrix.yaml); PROCESS elements (canon/elements/02_business/processes/); Process Blueprint (*.process-blueprint.transitrix.yaml). |
Rule: the word Activity MUST NOT be used to describe project-domain work items. The word Action MUST NOT be used to describe process-domain steps. Validators that detect notation: activity on a project-schedule document (distinct from notation: bpmn / PROCESS flow contexts) MUST emit ACTION-005.
Historical note. Prior to 2026-06-25 the project-domain primitive was called ACTIVITY. That name has been deprecated in favour of ACTION to enforce this distinction. The deprecated ACTIVITY TYPE prefix, activity_type field, and activities: array name are accepted with ACTION-005 / ACT-020 warnings until the 1.0 cut; see IDS_AND_REFERENCES.md §6 for the migration checklist.