# Roadmap and initiatives
An **initiative** is a strategic outcome tracked in `tix.toml` and connected to the
work that delivers it. The **roadmap** is a projection over the initiative registry
that joins the existing project, epic, milestone, planning and health services:
it stores no second metric, owns no portfolio service, and never changes an issue's
ownership or references.
```text
tix initiative create <name> [--outcome <text>] [--owner <id>] [--status <state>]
[--priority <p>] [--labels <a,b>] [--target-date <YYYY-MM-DD>]
[--projects <id,…>] [--epics <id,…>] [--milestones <name,…>]
tix initiative edit <id> [--name <n>] [--outcome …] [--owner …] [--status …]
[--priority …] [--labels …] [--target-date …]
[--projects …] [--epics …] [--milestones …]
tix initiative list
tix initiative show <id>
tix initiative delete <id> [--yes]
tix roadmap list [--from <date> --to <date> --owner <id> --status <state>
--label <label> --query <text>]
tix roadmap show <id>
tix export --target roadmap [--output <path>]
```
In the TUI, `F10` opens the Portfolio page and `Tab`/`Shift+Tab` cycle to its
**Roadmap** tab: one row per initiative in roadmap order, with `c` create, `e`
rename, `a` associate, `x` dereference, `f` filter, `r` re-read and `j`/`k` to move
the row cursor. Agents reach the same values through MCP: the initiative management
tools (`list_initiatives`, `get_initiative`, `create_initiative`, `update_initiative`,
`delete_initiative`, `initiative_associate`, `initiative_dereference`) and the
roadmap reads (`roadmap_list`, `roadmap_show`, `roadmap_summary`).
## The record
An initiative lives in an ordered `[initiatives.<id>]` registry beside its
`[projects.<id>]` and `[epics.<id>]` siblings:
```toml
[initiatives."i-3f9a2c"]
name = "Reach 99.9% availability"
outcome = "No customer-visible outage for two consecutive quarters"
owner = "alice"
status = "active"
priority = "high"
labels = ["reliability", "2026"]
target_date = "2026-12-31"
projects = ["p-7b1e04"]
epics = ["e-2c9d"]
milestones = ["2026-Q4"]
```
The **id** is the identity — the registry key, allocated as a stable generated id and
never reused. The **name** is a display label: `tix initiative edit --name` changes
it, and every association and derived value survives because nothing is re-keyed.
The section is omitted entirely when the registry is empty, and every change goes
through the existing atomic `tix.toml` save: a rejected change writes no byte.
An initiative change appends **no** `## History` event. `## History` belongs to an
issue, and an initiative is a configuration record: nothing in this surface writes an
issue file, so there is no subject for an event. The audit is the atomic registry
write plus version control, exactly as `[projects]` and `[epics]` do it.
## Associations
References are **explicit** and typed: a project by its stable **id**, an epic by its
stable **id**, and a milestone by its **name** (matching the existing name-keyed
milestone registry). A record may carry several of each; a repeated reference
collapses to one entry.
A reference is shape-checked when it is written but **never resolved at write time**.
A well-formed id that names nothing is stored and stays diagnosable — it is never
silently dropped, never inferred from the owner, and never repaired by an
association change. Deleting an initiative removes the registry entry only: no
project, epic, milestone or issue is deleted or modified. A repeated `associate` or a
`dereference` of a reference that is not recorded is an idempotent no-op that writes
no bytes.
## The filter
Every surface narrows the roadmap through the same normalized DTO: the CLI flags, the
TUI filter editor, the MCP arguments and the portable document all parse into one
`RoadmapFilter` with optional inclusive period bounds, owner, initiative status,
labels and free-text query. There is no second filter grammar to drift from the
first.
The two halves of a filter are deliberately asymmetric:
- **syntax is refused** — a date that is not `YYYY-MM-DD`, a period whose lower bound
is after its upper bound, or a blank owner/status/label is a named refusal raised
*before* anything is projected;
- **resolution is not** — a well-formed value that names nothing (a status no
initiative carries, an owner nobody has) simply matches nothing and returns an
empty ordered set, so a filter is usable before the records it names exist.
An empty filter is the documented "no filter" value and matches every initiative.
Matching is case-insensitive and trimmed for owner, status and labels; labels are an
**AND** over every requested value; the query is a case-insensitive substring of any
one searchable field (`name`, `outcome`, `owner`, `status`, `labels`).
## Ordering
Rows are ordered by **target date ascending, then stable initiative id**. The id
tie-break makes the order total and deterministic for a given registry, independent
of declaration order.
A missing target date is the explicit **`undated`** ordering state, rendered as
`undated` — never a blank, never an invented period, and never a sentinel date. An
undated initiative sorts **after** every dated one and keeps its relative id order. A
period filter is a different matter: an undated initiative cannot satisfy a calendar
bound and is excluded by a bounded filter.
## The summary is a join, not a blend
Each row resolves its references into an `InitiativeSummary` that carries **one
contribution per referenced source entity**:
- the entity's kind and stable id, spelled as `<kind>:<id>` provenance
(`project:p-7b1e04`, `epic:e-2c9d`, `milestone:2026-Q4`);
- the entity's derived display label and its resolution (`resolved`, `missing`,
`ambiguous`);
- the **owning service's own value** — a project's report from the project service,
an epic's rollup from the epic service — never a recomputation;
- the shared health policy's typed risk state and the **policy version** that
produced it (`health-v1`), so two risk readings are only compared under the same
formula.
There is deliberately **no** aggregate percentage, score, average or rate: inventing
one would make the roadmap a second metrics engine. Where a structural count is
unavoidable it is a **count of contributions**, never a derived rate.
The explicit summary states are exhaustive and never conflated:
| `complete` | Every recorded reference resolved and at least one reports a knowable value |
| `partial` | Some references resolved, at least one did not |
| `empty` | The initiative records no reference at all |
| `missing_reference` | At least one reference is recorded and none resolve |
| `unknown` | Every reference resolved, but no source reports anything knowable |
A genuine **zero** is a value, not an absence: a project with `0/0` progress is
knowable, so it yields `complete` with a `0/0` figure — never `empty`, never
`unknown`, and never a dash. "Nobody said" and "nothing is referenced" are the two
distinct states `unknown` and `empty`.
## Diagnostics and the lint boundary
A missing reference stays visible everywhere: the row prints it with its resolution
(`[missing]`), the detail pane repeats it, and the ordered diagnostics name the
record **field**, the reference and a remedy. The order is stable (`kind`,
`initiative`, `field`, `reference`, `source`, `message`), so two reads of one
snapshot list them identically. Zero-progress, unknown, empty and missing-reference
states are never collapsed into one another.
`tix lint` reports **issue-held** project and epic references, and an epic registry's
own references, as findings on the issue or record that holds them. An **initiative's**
references are config-held in `[initiatives.<id>]` and are **not** a lint family:
they are diagnosed on the roadmap surfaces (the row, the detail and the diagnostics),
which is where an initiative is read. Repair a dangling initiative reference by
declaring the referenced entity or removing the reference from the initiative.
## The portable document and remote mapping
`tix export --target roadmap` writes a versioned, byte-stable JSON document
(`format: "tix-roadmap"`, `version: 1`) built from the one projection every surface
reads, so `tix roadmap list`, the TUI tab, MCP and the export cannot disagree about
the same snapshot. It carries each row's id, metadata, references with resolution,
per-source contributions with their provenance and policy versions, the normalized
filter, and the ordered diagnostics; a document from a version this build cannot read
is refused by name rather than guessed at. A round trip is a fixed point: reading and
rewriting a document of this version preserves what was there, including unknown
keys, a missing reference, an empty summary and a zero figure.
When an `[export.gitlab]` or `[export.linear]` target is configured, the document
also carries one **remote-mapping diagnostic per initiative per target**, kind
`unsupported_remote_field`, naming the remote field and the target. The finding is
purely additive: the local record is untouched, so a reader sees *why* an initiative
stays local rather than being asked to trust that it did. The remote half reuses the
shared `sync` `RemoteMappingDiagnostic` and its field vocabulary rather than defining
a second one; the roadmap's remote contribution is these unsupported-field
diagnostics only — it publishes no remote project or epic summary.
## What the roadmap never does
- It does not introduce a hosted portfolio store or a new record format: the anchor
is the `[initiatives.<id>]` registry in `tix.toml`, and Markdown plus configuration
remain canonical.
- It does not own a second metric or a second ownership policy: progress and risk
come from their existing services under the shared health policy version.
- Applying or filtering a roadmap view mutates nothing — no issue, project, epic or
milestone is written by a read.