finos / finos/architecture-as-code
Branching timelines: model multiple potential futures of an architecture
- Dominant language
- TypeScript
- Stars
- 399
- Forks
- 138
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 37
Description
## CALM Schema Change Proposal
### Target Schema:
`meta/timeline.json` (and its `calm-timeline.json` wrapper) — an additive change to the `node-moment` definition.
### Description of Change:
Today a CALM timeline is a **flat, chronologically-ordered list of moments** with a single `current-moment` and one implied future. This proposal lets a timeline branch into a **tree of moments** so teams can model **multiple potential futures** of an architecture — comparing competing options side by side (e.g. *buy vs build*, *Kafka vs Pulsar*), committing to one, and retaining the rejected alternatives as a navigable record of what was considered and why.
Two new **optional** moment fields, plus a third for rationale:
- **`predecessor`** *(string)* — the `unique-id` of the moment this one follows. Two moments sharing a `predecessor` form a fork (competing futures). Chosen over `parent` because it reads as temporal succession, not containment.
- **`status`** *(enum: `proposed` | `chosen` | `rejected` | `superseded`)* — the decision state of a future branch.
- **`decision-reason`** *(string)* — why a branch was rejected/superseded (complements existing `adrs` links).
### Use Cases:
- **Decision exploration**: lay out candidate futures with their own concrete architectures, weigh trade-offs/ADRs per branch, commit to one (`chosen`).
- **Auditable record of alternatives**: rejected options remain in the document as evidence of what was considered and why — valuable in regulated environments.
- **Navigable what-ifs**: every branch is a full moment with its own `detailed-architecture`, so never-realized futures can still be viewed and walked.
### Current Limitations:
The flat array models only "how we got here + one planned next step." It cannot express "we are here, weighing three different futures," nor retain the rejected alternatives and the reasoning behind the choice — there is no branch concept and no decision status.
### Proposed Schema Changes:
```json
{
"defs": {
"node-moment": {
"properties": {
"predecessor": {
"type": "string",
"description": "The unique-id of the moment this moment follows. Moments sharing a predecessor form a fork of competing potential futures. Defines the timeline tree."
},
"status": {
"enum": ["proposed", "chosen", "rejected", "superseded"],
"description": "Decision state of a branch: proposed (under consideration), chosen (committed future), rejected (considered and declined), superseded (was chosen, later replaced)."
},
"decision-reason": {
"type": "string",
"description": "Why this branch was rejected or superseded."
}
}
}
}
}
```
All three are optional; `details.detailed-architecture` remains required on every moment.
### Backward Compatibility:
**Fully backward compatible** — mode is inferred per document:
- **No moment declares `predecessor`** → *legacy linear mode*: array order is the chain, exactly as today. Existing documents remain valid with zero edits.
- **Any moment declares `predecessor`** → *tree mode*: the tree is built entirely from `predecessor`; array order is meaningless; exactly one moment omits `predecessor` (the root).
Mixing (some pointers, some implicit order) is ambiguous and rejected by validation. No breaking changes; no migration required.
### Validation Strategy:
New/adapted Spectral rules (each with passing + failing fixtures):
- Structural (tree mode): all-or-none `predecessor`; single root; `predecessor` references an existing moment; acyclic.
- Decision semantics: at most one `chosen` child per moment; chosen path may not pass through a `rejected`/`superseded` moment; `proposed` only on non-realized branches.
- Chronology, generalized from today's rules to a tree via one invariant: **`valid-from` iff the moment lies on the realized-history path (root → `current-moment`)**. Branches are never dated (they were never valid) but stay fully navigable. Dates increase monotonically along the realized path. This single rule subsumes the existing `moments-sorted-by-valid-from`, `current-moment-must-be-last-when-no-valid-from`, and future-omits-`valid-from` rules.
### Implementation Impact:
- **Schema**: additive fields on `node-moment`.
- **Models** (`calm-models`): extend `CalmMomentSchema`; add tree helpers to `CalmTimeline`/`CalmMoment` (`root()`, `children()`, `realizedPath()`, `committedPath()`, mode detection).
- **Validation** (`shared`): the Spectral rules above.
- **Docs**: new "Branching timelines" section in `core-concepts/timelines.md`.
- **Follow-on work requires separate issues** — notably **visualisation in `calm-hub-ui`** (rendering the branching timeline as a tree: fork layout, NOW badge, chosen path highlighted, rejected branches dimmed, navigation into each branch's `detailed-architecture`). Also branch-aware `timeline-diff` and AI authoring updates (`calm-ai`). This issue does **not** cover them.
### Version Strategy:
Minor, additive, non-breaking — no major bump. Which schema version it targets is a scheduling decision left to whoever picks up the work, not prescribed here.
### Implementation Checklist:
- [ ] Schema changes drafted and validated
- [ ] Example documents created that validate against the new schema
- [ ] Tests written to verify schema behavior
- [ ] Documentation updated to reflect changes
- [ ] Migration guide created (if applicable) — N/A, fully backward compatible
- [ ] Related schemas updated (if applicable)
### Additional Context:
Scope deliberately limited to the **language** (schema + models + validation + docs). **Visualisation of branching timelines in `calm-hub-ui` is explicitly out of scope here and will need its own follow-on issue**, as will branch-aware diff and AI authoring support. Out of scope entirely: counterfactual *pasts* (history stays a single realized path), multiple concurrent `current-moment` baselines, branch *merging* (model is a tree, not a DAG), and numeric likelihood weighting.
Contributor guide
Research direction
Start with meta/timeline.json and its calm-timeline.json wrapper, then inspect CalmMomentSchema and the CalmTimeline/CalmMoment model entry points. Review existing Spectral rules and their passing and failing fixtures before defining tree validation. Done means the additive schema, model helpers, validation tests and fixtures, and the Branching timelines section in core-concepts/timelines.md are complete.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- documentation, testing-qa, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 42/100