1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! Roadmap ordering: chronological target dates, stable-ID tie-breaks (WP02, FR-003).
//!
//! One pass over the registry ([`filter_and_order`]) applies the normalized
//! [`RoadmapFilter`] and sorts the survivors. Nothing here reads a file, touches
//! the clock, or mutates the registry: the registry is already in memory, and
//! filtering and ordering are read-only derived views (contract
//! `roadmap-projection.md`).
//!
//! # The ordering rule
//!
//! Target date **ascending**, then stable initiative **id** ascending. A validated
//! `YYYY-MM-DD` date compares chronologically as a plain string (fixed-width,
//! zero-padded), so the date key is the string itself — no parsed date type and no
//! second date engine. The id tie-break makes equal dates deterministic: ids are
//! unique and never reused (C-002), so the order of a given registry is total and
//! independent of declaration order.
//!
//! # The missing-date state
//!
//! An initiative with no target date is **not** filtered out and is **not** given
//! an invented period ("today", the epoch, `0000-00-00`). It is the explicit
//! [`TargetDateState::Undated`] state on its row, and it sorts **after** every dated
//! initiative. A dated initiative always sorts before an undated one; undated
//! initiatives keep their relative order by stable id. Rendering that state is the
//! projection's job (WP03) — this module only guarantees it is discoverable per row
//! and that it never collides with a real date.
//!
//! A *period filter* is a different matter from ordering: an undated initiative
//! cannot satisfy a calendar bound and is excluded by a bounded filter
//! ([`RoadmapFilter::matches`]), because inventing a period for it is exactly what
//! C-003 forbids.
use crate;
use crateResult;
use crateRoadmapFilter;
/// The explicit ordering state of one initiative's target date.
///
/// The two states are exhaustive and never conflated: [`Self::Dated`] carries a
/// validated `YYYY-MM-DD` calendar date, and [`Self::Undated`] is a record that
/// carries no target date at all. There is no third "unknown date" value and no
/// sentinel date, so a surface cannot render an undated initiative as if it were
/// scheduled (C-003).
/// One initiative selected for the roadmap, with its stable id and ordering state.
///
/// Deliberately a *view*: it borrows the registry record rather than copying it, and
/// it resolves the target-date state once (at construction) so sorting never
/// re-inspects the record. The projection (WP03) owns the displayable row; this is
/// the filter/order layer's input to it.
/// Sort `entries` in place by target date ascending, then stable id ascending.
///
/// The sort is stable, and the id tie-break makes the order total for a registry
/// whose ids are unique, so the result is deterministic even for equal dates. A
/// missing target date sorts after every dated entry.
/// Filter the registry and order the survivors: the one read-only roadmap pass.
///
/// Validates the filter's syntax **first**, so a malformed bound refuses before any
/// record is inspected; then walks `config.initiatives` once, keeps every record the
/// filter matches, and sorts. Equivalent filters therefore produce equivalent
/// ordered id sets on every surface (C-007, NFR-003), because there is exactly one
/// predicate and one sort.
///
/// `config` is borrowed immutably: the pass can neither mutate nor persist an
/// initiative.