kernel/spine/row.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! Roadmap rows: the types a table is read into.
7//! The part `crates/kernel/spec/Spine.lean` specifies this module with the
8//! rest of `kernel::spine`.
9
10use serde::{Deserialize, Serialize};
11
12use crate::locator::Locator;
13use crate::node_id::NodeId;
14
15/// The five states, closed. The spellings below are the canonical table
16/// strings, and there is exactly one set of them: a second set would be a
17/// second authority over what a resident is allowed to write.
18///
19/// The serde spellings are `snake_case` identifiers rather than the table
20/// words: a wire frame is read by a program and a table cell by a person,
21/// and making the client carry `Awaiting approval` would put an English
22/// sentence where the phrase table belongs.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
26pub enum RoadmapStatus {
27 NotStarted,
28 InProgress,
29 Done,
30 Blocked,
31 AwaitingApproval,
32}
33
34/// Status column spellings — the parse table, data not code. These are
35/// the words the template asks a resident to write.
36pub const ROADMAP_STATUS_SPELLINGS: [(RoadmapStatus, &str); 5] = [
37 (RoadmapStatus::NotStarted, "Not started"),
38 (RoadmapStatus::InProgress, "In progress"),
39 (RoadmapStatus::Done, "Done"),
40 (RoadmapStatus::Blocked, "Blocked"),
41 (RoadmapStatus::AwaitingApproval, "Awaiting approval"),
42];
43
44/// The number of columns the roadmap table has. Named because three
45/// places state it — the parser, the refusal it writes, and the
46/// template — and a plan whose columns disagree with its reader has no
47/// denominator at all.
48pub const ROADMAP_COLUMNS: usize = 6;
49
50/// The name of the plan file, in every building at every depth.
51///
52/// Named here beside the table's own grammar because the file and the
53/// table are one fact: `city` spells the path from it, and
54/// `write_domain` refuses it to a documents domain, so a rename moves
55/// one line rather than three.
56pub const ROADMAP_FILE: &str = "Roadmap.md";
57
58impl RoadmapStatus {
59 /// The one spelling, for every writer and every message.
60 #[must_use]
61 pub fn spelling(self) -> &'static str {
62 ROADMAP_STATUS_SPELLINGS
63 .iter()
64 .find(|(known, _)| *known == self)
65 .map_or("Not started", |(_, spelling)| *spelling)
66 }
67}
68
69/// The evidence column, three-way: empty and invalid are different facts
70/// — one renders as "in progress", the other as "suspect".
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum EvidenceCell {
73 Empty,
74 Invalid { raw: String },
75 Present(Locator),
76}
77
78/// One row, as the table spells it.
79///
80/// `weight` is a ratio among the rows that share a parent and never a
81/// quantity, so doubling every number on one level changes nothing. An
82/// empty cell reads as 1: a plan that says nothing about how a level
83/// divides is a plan that divides it evenly.
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct RoadmapRow {
86 pub id: NodeId,
87 pub item: String,
88 pub weight: u32,
89 pub needs: Vec<NodeId>,
90 pub status: RoadmapStatus,
91 pub evidence: EvidenceCell,
92}
93
94/// Deliberately exhaustive shape verdict.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum RoadmapShape {
97 WellFormed { rows: Vec<RoadmapRow> },
98 Malformed { problems: Vec<String> },
99}
100
101/// A child to hang under a node: what it is, and how big a slice of its
102/// parent it takes.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct NewChild {
105 pub item: String,
106 pub weight: u32,
107}