Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 114 additions & 1 deletion src/docs/arc42/04-solution-strategy/doc-04001-metamodel.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ title: Architecture Metamodel
status: draft
owner: architecture-knowledge-toolkit maintainers
created: '2026-06-25'
updated: '2026-09-21'
reviewed: false
generated: false
summary: Defines the small metadata and relationship model that enables validated and generated architecture knowledge.
Expand Down Expand Up @@ -76,7 +77,7 @@ suggestion until a reviewer accepts it.

| `status`
| yes
| Lifecycle state such as `draft`, `proposed`, `accepted`, or `superseded`.
| Review state of the artifact. See xref:artifact-lifecycle-status[] for the defined values and for the separate validity axis.

| `owner`
| yes
Expand Down Expand Up @@ -155,6 +156,118 @@ traceability between artifacts.
| Supporting documentation that participates in traceability.
|===

[[artifact-lifecycle-status]]
=== Artifact Lifecycle Status

An artifact carries two lifecycle axes. `status` answers how confirmed the
artifact is, and `validity` answers whether it still holds. Both can be true at
once: an accepted risk that has been mitigated away is `status: accepted` and
`validity: retired`. Keeping the axes apart is decided in
xref:adr-009-artifact-retirement-and-relation-validity[].

The separation is not complete yet. `superseded` and `deprecated` are temporal
statements that still sit on the review axis, and ADR-009 deliberately left
them there instead of migrating them with the same decision, because that
migration overlaps with deriving `superseded_by` from an outgoing `supersedes`
relation. Read them as transitional values: every other `status` value is a
review verdict, and new temporal information belongs on `validity`.

`status` is the review axis, apart from those two transitional values.

[cols="1,3", options="header"]
|===
| Status | Meaning

| `draft`
| Work in progress. Incomplete notes or an artifact that is not yet a coherent
proposal. Not architecture truth.

| `proposed`
| Complete enough to review, but not reviewed. AI-created artifacts start here.

| `reviewed`
| A human has checked the content without deciding to adopt it.

| `accepted`
| Adopted as the repository's architecture knowledge. Only reachable through
human review. For a `Risk` artifact this means the risk description is accepted
knowledge, not that the risk itself is deliberately carried; that belongs in the
risk's own acceptance section.

| `rejected`
| Considered and intentionally not adopted. The artifact stays in the repository
as the record of an option that was turned down.

| `superseded`
| Transitional, temporal. Replaced by a newer artifact, which is named in
`superseded_by`. The newer artifact carries the `supersedes` relation.

| `deprecated`
| Transitional, temporal. Still in force, but discouraged for new work. Use it
where something is being phased out and can still be encountered, such as a
component, an interface, or a concept. Do not use it for an artifact whose
subject no longer exists at all; that is retirement on the validity axis.
|===

`validity` is the temporal axis. It is optional, and an absent value means
`active`. Validator and generator implement that reading explicitly; a schema
`default` is an annotation and materializes nothing.

[cols="1,3", options="header"]
|===
| Validity | Meaning

| `active`
| The artifact still holds. This includes a risk that the team knowingly
carries: a deliberately accepted residual risk is still present, so it stays
active and records its acceptance in the risk artifact.

| `retired`
| The artifact's subject no longer exists or no longer applies. The artifact
stays in the repository with its stable ID and its review status.
|===

A retired artifact records when and why:

[cols="1,3", options="header"]
|===
| Field | Meaning

| `retired_on`
| Date of retirement, on or after `created`.

| `retired_reason`
| Machine-readable reason, for filtering and generated views.
`no-longer-applicable` and `removed` apply to every artifact type. `Risk`
artifacts add `mitigated` and `materialized`.

| `retired_note`
| Free text that explains the retirement to a human reader. The reason is for
machines, the note is for people; neither replaces the other.
|===

The three fields belong to retirement and are absent while an artifact is
active.

Retirement never rewrites the relations that touch the artifact. A relation
keeps the relationship status it was reviewed with, because it stated something
that was true when it was made. `rejected` is a verdict about the claim itself,
for example that a decision never mitigated the risk it named, and not a way to
express that time has passed.

A relation is inactive for the current architecture when either endpoint is
retired, not only its target. Generated impact and traceability matrices keep
rendering an inactive relation and mark it as such. Authoring a new relation to
a retired artifact stays allowed, because a historical link such as `documents`
or `supersedes` is legitimate; the validator warns about it so an accidental
reference to obsolete knowledge becomes visible.

NOTE: The validity axis is decided in
xref:adr-009-artifact-retirement-and-relation-validity[] but not yet
implemented. `metamodel/artifact.schema.yaml` rejects unknown metadata fields,
so do not add `validity`, `retired_on`, `retired_reason`, or `retired_note` to
artifact metadata before the schema and the validator accept them.

=== Relationship Types and Semantics

Relationships are directed: `source --type--> target`. Relationship semantics
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
---
id: ADR-009-artifact-retirement-and-relation-validity
type: ADR
title: Artifact Retirement and Relation Validity Over Time
status: accepted
owner: architecture
created: '2026-09-21'
updated: '2026-09-21'
reviewed: true
summary: Accepted decision to keep relationship status a review state and to express artifact retirement on a separate validity axis that generated matrices render as inactive.
derived_from:
- type: conversation
description: Maintainer question whether a mitigates relation must become rejected once the target risk no longer exists, and whether deprecated is the right status for a risk that is closed rather than discouraged.
tags:
- decision
- metamodel
- traceability
relations:
- type: refines
target: DOC-04001-metamodel
status: accepted
rationale: This ADR adds a validity axis to artifact metadata and fixes the meaning of relationship status when a relation target stops being current.
evidence: src/docs/arc42/04-solution-strategy/doc-04001-metamodel.adoc
reviewed: true
- type: addresses
target: QS-004-ai-suggestion-reviewability
status: accepted
rationale: Keeping review maturity separate from temporal validity preserves status as the marker that tells a reviewer whether content is suggested or accepted.
evidence: src/docs/arc42/10-quality-requirements/qs-004-ai-suggestion-reviewability.adoc
reviewed: true
- type: introduces_risk
target: R-004-heavy-metadata-authoring
status: accepted
rationale: A second lifecycle axis adds another optional metadata field that authors must understand and maintain.
evidence: src/docs/arc42/11-risks-and-technical-debt/r-004-heavy-metadata-authoring.adoc
reviewed: true
metadata_version: '1.0'
---
[[adr-009-artifact-retirement-and-relation-validity]]
== ADR-009: Artifact Retirement and Relation Validity Over Time

=== Status

Accepted (derived).

The rationale was AI-drafted from a maintainer conversation and then reviewed
and accepted by the accountable owner. The derived marker records that the
drafting was AI-assisted; the acceptance is human.

=== Decision

`relationshipStatus` stays a review state only. Whether an artifact is still
current is expressed on a separate validity axis in artifact metadata:
`validity` with the values `active` and `retired`, plus `retired_on`, the
machine-readable `retired_reason`, and the free-text `retired_note`. An absent
`validity` means `active`, and validator and generator implement that reading
explicitly rather than relying on a schema default, which is an annotation and
materializes nothing.

A relation is inactive for the current architecture when **either** endpoint is
retired. An inactive relation keeps the relationship status it was reviewed
with and stays visible in generated impact and traceability matrices, marked
inactive, because it remains a true record of what was decided.

Authoring a new relation that points at a retired artifact stays allowed.
Historical relations such as `documents` or `supersedes` are legitimate and a
reconstructed traceability link may well target retired knowledge. The
validator warns about such a relation so an accidental reference to obsolete
knowledge becomes visible instead of passing silently.

Retirement means the subject is gone, not that it is deliberately carried. A
residual risk that the team knowingly accepts stays `validity: active`; its
acceptance belongs in the risk artifact, not on the validity axis.

=== Context

A relation such as `ADR-00X mitigates R-00Y` can outlive its target: the risk
is mitigated away, stops applying after an architecture change, or is merged
into another risk. The question is what happens to the relation, and what
happens to the risk artifact.

`relationshipStatus` is defined in
xref:architecture-metamodel[the architecture metamodel] as a review state:
`proposed` is unreviewed, `reviewed` is checked by a human, `accepted` is taken
into the repository's architecture knowledge, and `rejected` is considered and
intentionally rejected. `rejected` is therefore a verdict about the claim, not
about time. Setting an accepted `mitigates` relation to `rejected` because the
risk is gone would retroactively deny a statement that was true and reviewed
when it was made.

The artifact `status` enum in `metamodel/artifact.schema.yaml` offers `draft`,
`proposed`, `reviewed`, `accepted`, `rejected`, `superseded`, and `deprecated`.
The metamodel document lists these only as examples and never defines the
individual values, so `deprecated` has no repository-specific meaning; it
carries its ordinary document and API lifecycle sense of "still present, do not
use it for new work". A risk that no longer exists is not discouraged for
future use, it is closed. The enum also mixes two axes: review maturity
(`draft` through `rejected`) and temporal validity (`superseded`,
`deprecated`). Replacement is already modelled by `superseded` together with
the `superseded_by` field; retirement without a replacement has no
representation at all.

For `Risk` artifacts the mixed axis additionally collides with domain
vocabulary: `status: accepted` means "accepted as architecture knowledge" in
this metamodel, while an accepted risk in risk management means "known and
deliberately carried". Both readings are plausible on the same artifact today.

The generator in `scripts/validate-metamodel.rb` renders an artifact status
column and relation cells, but has no concept of a relation that was valid and
is not any more.

=== Decision Drivers

* Keep the history of an accepted relation intact instead of rewriting a
statement that was true when it was reviewed.
* Make retirement machine-readable so generated matrices can mark a relation
inactive without hiding it.
* Keep review maturity and temporal validity separately readable on the same
artifact.
* Avoid status vocabulary that means different things per artifact type.
* Keep the authoring burden small, because metadata weight is already tracked
by xref:r-004-heavy-metadata-authoring[].

=== Considered Options

==== Option 1: Add a closure value to the shared status enum

Add `closed` to the artifact `status` enum and use it for risks that no longer
exist. One new value, no new field, and the generator keys its inactive marker
on the target's status.

==== Option 2: Separate validity axis

Add an optional `validity` field with `active` and `retired`, together with
`retired_on`, a machine-readable `retired_reason`, and a free-text
`retired_note`. `status` keeps answering how confirmed an artifact is,
`validity` answers whether it still holds. The generator keys its inactive
marker on the validity of the relation's endpoints.

==== Option 3: Prose-only closure note

Record closure in the risk body, for example in `=== Decision or Acceptance`,
and leave metadata untouched.

=== Pugh Matrix

[.pugh-matrix]
[cols="2,1,1,1", options="header"]
|===
| Criterion | Status value | Validity axis | Prose only
| Generated matrices can mark relations inactive | +1 | +1 | -1
| Review maturity stays readable after retirement | -1 | +1 | +1
| No vocabulary collision per artifact type | -1 | +1 | 0
| Works uniformly for every artifact type | -1 | +1 | 0
| Small schema and low authoring burden | +1 | -1 | +1
| Retirement date and reason are validatable | 0 | +1 | -1
s| Sum s| -1 s| 4 s| 0
|===

=== Consequences

==== Positive

* An accepted relation stays accepted, so the traceability history remains a
truthful record of what was decided and why.
* Retirement is machine-readable, so generated impact and traceability
fragments can render the relation as inactive rather than dropping it.
* A retired risk keeps its review state, so reviewers can still see whether the
risk was ever accepted as architecture knowledge.
* The axis generalizes beyond risks to retired components, interfaces,
constraints, and quality scenarios.
* A generated risk register can filter open risks without interpreting review
vocabulary.

==== Negative

* The metamodel gains a second axis with four optional fields, which increases
the authoring surface tracked by xref:r-004-heavy-metadata-authoring[].
* Relations to retired artifacts produce validator warnings, and without a way
to record a deliberate historical link those warnings can accumulate as
permanent noise.
* An artifact's state must be read from two fields instead of one.
* Validator, generator, templates, and the `example/metamodel/` schema copy all
have to learn the second axis, and consuming projects that vendored the
schema need a migration note.

==== Neutral or Follow-Up

* `superseded` and `deprecated` stay in the `status` enum for now. A follow-up
decision has to say whether they move to the validity axis or keep their
document and API lifecycle meaning.
* How the generator marks an inactive row, for example a marker column, a
footnote, or struck-through text, is a rendering decision made when the
generator changes.
* The artifact status table that the metamodel document was missing is written
with this decision, so every status value and both lifecycle axes have a
defined meaning in xref:architecture-metamodel[].

=== Impact

include::generated/adr-009-artifact-retirement-and-relation-validity-impact.adoc[leveloffset=+2]

=== Traceability

include::generated/adr-009-artifact-retirement-and-relation-validity-attributes.adoc[]

ifdef::derived_from_description[]
==== Derived from
{derived_from_description}
endif::[]

include::generated/adr-009-artifact-retirement-and-relation-validity-traceability.adoc[leveloffset=+2]

=== Assumptions

* Retirement is a human decision recorded in the repository, like acceptance;
an agent may only propose it.
* Reviewers want a retired relation to stay visible as history rather than
disappear from the matrix.
* A small reason vocabulary is enough: `no-longer-applicable` and `removed` for
every artifact type, plus `mitigated` and `materialized` for risks.

=== Open Questions

These belong to the implementation slice. The decision holds regardless of how
they are answered; they shape the schema and the rendering, not the axis
separation.

* Do `superseded` and `deprecated` remain status values, or migrate to the
validity axis? This overlaps with the derivation of `superseded_by` from an
outgoing `supersedes` relation.
* How does an author record that a relation to a retired artifact is
deliberately historical, so its warning does not become permanent build
noise?
* How does a generated matrix mark an inactive relation, for example a marker
column, a footnote, or struck-through text?

=== Review Notes

Accepted after review of the decision itself: that review maturity and temporal
validity are separate axes, and that a relation to a retired artifact keeps its
reviewed status instead of being rejected.

The impact claims recorded as relations were accepted with the decision. Their
targets remain proposed artifacts in their own right; an accepted relation
states that the claim holds, not that the artifact it points at has been
accepted.

Acceptance does not cover the implementation slice.
`metamodel/artifact.schema.yaml` carries none of the new fields yet, and the
generator has no inactive marker. Do not put the new fields into artifact
metadata before the schema and the validator accept them, and decide the
migration note for projects that vendored the schema before any schema change
is released.

Amended after a convergence check against the implementation issue. The
amendment closed four semantic gaps that the first version had left to the
implementation: a relation is inactive when either endpoint is retired, not
only its target; a new relation to a retired artifact is allowed and warned
about rather than rejected; a deliberately carried residual risk stays active,
so residual acceptance is not a retirement reason; and the machine-readable
reason is separated from its explanatory note. The axis separation itself was
not reopened.
Loading