rototo 0.1.0-alpha.2

Control plane for runtime configuration in application code.
Documentation
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
use crate::error::{Result, RototoError};

#[derive(Clone, Copy, Debug, serde::Serialize)]
pub struct DocPage {
    pub id: &'static str,
    pub title: &'static str,
    pub list_title: &'static str,
    pub markdown: &'static str,
}

#[derive(Clone, Copy, Debug, serde::Serialize)]
pub struct DocNavSection {
    pub title: &'static str,
    pub pages: &'static [&'static str],
}

#[derive(Debug)]
pub enum DocPageLookup {
    Found(&'static DocPage),
    Ambiguous {
        query: String,
        pages: Vec<&'static DocPage>,
    },
    NotFound {
        query: String,
        pages: Vec<&'static DocPage>,
    },
}

pub const DOCS: &[DocPage] = &[
    DocPage {
        id: "index",
        title: "rototo introduction",
        list_title: "Introduction: runtime configuration control plane, workspace model, and refresh loop",
        markdown: include_str!("../docs/src/index.md"),
    },
    DocPage {
        id: "why-rototo",
        title: "Why rototo",
        list_title: "Why rototo: runtime configuration failure modes and the Git-backed control-plane model",
        markdown: include_str!("../docs/src/concepts/why-rototo.md"),
    },
    DocPage {
        id: "rototo-model",
        title: "The rototo Model",
        list_title: "Model: workspaces, environments, qualifiers, variables, schemas, values, and resolution",
        markdown: include_str!("../docs/src/concepts/rototo-model.md"),
    },
    DocPage {
        id: "quickstart",
        title: "Quickstart",
        list_title: "Quickstart: create a local workspace, lint it, and resolve one variable",
        markdown: include_str!("../docs/src/tutorials/quickstart.md"),
    },
    DocPage {
        id: "production-workflow",
        title: "Production Workflow",
        list_title: "Production workflow: Git review, schemas, tests, app loading, refresh, and observability",
        markdown: include_str!("../docs/src/tutorials/production-workflow.md"),
    },
    DocPage {
        id: "how-to-add-a-new-runtime-config-value",
        title: "How to Add a New Runtime Config Value",
        list_title: "Add a runtime config value: define values, defaults, rules, and validation",
        markdown: include_str!("../docs/src/how-to/how-to-add-a-new-runtime-config-value.md"),
    },
    DocPage {
        id: "how-to-change-a-config-value-for-one-environment",
        title: "How to Change a Config Value for One Environment",
        list_title: "Change one environment: update an environment block without changing other deployments",
        markdown: include_str!(
            "../docs/src/how-to/how-to-change-a-config-value-for-one-environment.md"
        ),
    },
    DocPage {
        id: "how-to-add-a-new-context-field",
        title: "How to Add a New Context Field",
        list_title: "Add a context field: extend request facts, schema validation, and qualifier usage",
        markdown: include_str!("../docs/src/how-to/how-to-add-a-new-context-field.md"),
    },
    DocPage {
        id: "how-to-select-a-value-for-a-runtime-condition",
        title: "How to Select a Value for a Runtime Condition",
        list_title: "Select by runtime condition: create a qualifier-backed rule for one variable",
        markdown: include_str!(
            "../docs/src/how-to/how-to-select-a-value-for-a-runtime-condition.md"
        ),
    },
    DocPage {
        id: "how-to-move-large-values-out-of-toml",
        title: "How to Move Large Values Out of TOML",
        list_title: "Move large values out of TOML: split variable values into directory-backed files",
        markdown: include_str!("../docs/src/how-to/how-to-move-large-values-out-of-toml.md"),
    },
    DocPage {
        id: "how-to-test-a-config-change-before-merge",
        title: "How to Test a Config Change Before Merge",
        list_title: "Test before merge: lint and resolve representative cases for a config change",
        markdown: include_str!("../docs/src/how-to/how-to-test-a-config-change-before-merge.md"),
    },
    DocPage {
        id: "how-to-enforce-a-config-policy",
        title: "How to Enforce a Config Policy",
        list_title: "Enforce config policy: add workspace-specific Lua lint for variables and values",
        markdown: include_str!("../docs/src/how-to/how-to-enforce-a-config-policy.md"),
    },
    DocPage {
        id: "how-to-load-config-from-a-git-repo-in-an-app",
        title: "How to Load Config from a Git Repo in an App",
        list_title: "Load from Git in an app: point the SDK at a workspace source URI",
        markdown: include_str!(
            "../docs/src/how-to/how-to-load-config-from-a-git-repo-in-an-app.md"
        ),
    },
    DocPage {
        id: "how-to-keep-config-fresh-in-a-running-app",
        title: "How to Keep Config Fresh in a Running App",
        list_title: "Keep config fresh: refresh a running service while preserving last-known-good state",
        markdown: include_str!("../docs/src/how-to/how-to-keep-config-fresh-in-a-running-app.md"),
    },
    DocPage {
        id: "how-to-investigate-why-a-value-was-selected",
        title: "How to Investigate Why a Value Was Selected",
        list_title: "Investigate a selection: trace why a variable value was chosen at runtime",
        markdown: include_str!("../docs/src/how-to/how-to-investigate-why-a-value-was-selected.md"),
    },
    DocPage {
        id: "how-to-diagnose-a-failing-workspace",
        title: "How to Diagnose a Failing Workspace",
        list_title: "Diagnose lint failures: map stable diagnostics back to invalid workspace files",
        markdown: include_str!("../docs/src/how-to/how-to-diagnose-a-failing-workspace.md"),
    },
    DocPage {
        id: "example-environment-specific-limits",
        title: "Example: Keep Deployment-Lane Limits Out of Application Code",
        list_title: "Example: keep deployment-lane limits out of application code",
        markdown: include_str!("../docs/src/examples/example-environment-specific-limits.md"),
    },
    DocPage {
        id: "example-reviewed-account-class",
        title: "Example: Select Behavior for a Reviewed Account Class",
        list_title: "Example: select behavior for a reviewed account class",
        markdown: include_str!("../docs/src/examples/example-reviewed-account-class.md"),
    },
    DocPage {
        id: "example-llm-agent-configuration",
        title: "Example: Control Structured LLM Agent Config Safely",
        list_title: "Example: control structured LLM agent config with schema validation",
        markdown: include_str!("../docs/src/examples/example-llm-agent-configuration.md"),
    },
    DocPage {
        id: "example-tenant-specific-runtime-config",
        title: "Example: Manage Tenant Exceptions Without App Branches",
        list_title: "Example: manage tenant exceptions without application branches",
        markdown: include_str!("../docs/src/examples/example-tenant-specific-runtime-config.md"),
    },
    DocPage {
        id: "example-incident-banner",
        title: "Example: Ship an Operational Override Without Redeploying",
        list_title: "Example: ship an operational override without redeploying an app",
        markdown: include_str!("../docs/src/examples/example-incident-banner.md"),
    },
    DocPage {
        id: "example-bucketed-rollout",
        title: "Example: Run a Stable Percentage Rollout from Config",
        list_title: "Example: run a stable percentage rollout from workspace config",
        markdown: include_str!("../docs/src/examples/example-bucketed-rollout.md"),
    },
    DocPage {
        id: "workspace-manifest-reference",
        title: "Workspace Manifest Reference",
        list_title: "Reference: rototo-workspace.toml environments, schema version, and context schema",
        markdown: include_str!("../docs/src/reference/workspace-manifest-reference.md"),
    },
    DocPage {
        id: "qualifier-reference",
        title: "Qualifier File Reference",
        list_title: "Reference: qualifier TOML shape, predicates, references, and lint rules",
        markdown: include_str!("../docs/src/reference/qualifier-reference.md"),
    },
    DocPage {
        id: "variable-reference",
        title: "Variable File Reference",
        list_title: "Reference: variable TOML shape, values, env rules, schemas, and custom lint",
        markdown: include_str!("../docs/src/reference/variable-reference.md"),
    },
    DocPage {
        id: "predicate-reference",
        title: "Predicate Reference",
        list_title: "Reference: predicate fields, operators, value shapes, and bucket rules",
        markdown: include_str!("../docs/src/reference/predicate-reference.md"),
    },
    DocPage {
        id: "context-reference",
        title: "Context Reference",
        list_title: "Reference: runtime context contract and schema-backed request facts",
        markdown: include_str!("../docs/src/reference/context-reference.md"),
    },
    DocPage {
        id: "environment-reference",
        title: "Environment Reference",
        list_title: "Reference: environment declarations, defaults, and resolution fallback behavior",
        markdown: include_str!("../docs/src/reference/environment-reference.md"),
    },
    DocPage {
        id: "value-types-reference",
        title: "Value Types Reference",
        list_title: "Reference: primitive value types and schema-backed JSON values",
        markdown: include_str!("../docs/src/reference/value-types-reference.md"),
    },
    DocPage {
        id: "source-uri-reference",
        title: "Source URI Reference",
        list_title: "Reference: local, file, Git, SSH, HTTPS archive, ref, subdir, and auth forms",
        markdown: include_str!("../docs/src/reference/source-uri-reference.md"),
    },
    DocPage {
        id: "cli-reference",
        title: "rototo CLI reference",
        list_title: "CLI reference: commands, flags, context input forms, JSON output, and color behavior",
        markdown: include_str!("../docs/src/api/cli-reference.md"),
    },
    DocPage {
        id: "rust-sdk-reference",
        title: "rototo Rust SDK",
        list_title: "Rust SDK: async workspace loading, linting, reading, and resolution APIs",
        markdown: include_str!("../docs/src/api/rust-sdk-reference.md"),
    },
    DocPage {
        id: "diagnostic-reference",
        title: "Diagnostic reference",
        list_title: "Diagnostic reference: stable codes, rules, sources, severity, and help text",
        markdown: include_str!("../docs/src/api/diagnostic-reference.md"),
    },
    DocPage {
        id: "json-output-reference",
        title: "JSON Output Reference",
        list_title: "JSON output reference: machine-readable shapes emitted by CLI commands",
        markdown: include_str!("../docs/src/reference/json-output-reference.md"),
    },
];

pub const DOC_NAV_SECTIONS: &[DocNavSection] = &[
    DocNavSection {
        title: "Start",
        pages: &["index"],
    },
    DocNavSection {
        title: "Concepts",
        pages: &["why-rototo", "rototo-model"],
    },
    DocNavSection {
        title: "Tutorials",
        pages: &["quickstart", "production-workflow"],
    },
    DocNavSection {
        title: "How-to: Authoring",
        pages: &[
            "how-to-add-a-new-runtime-config-value",
            "how-to-change-a-config-value-for-one-environment",
            "how-to-add-a-new-context-field",
            "how-to-select-a-value-for-a-runtime-condition",
            "how-to-move-large-values-out-of-toml",
        ],
    },
    DocNavSection {
        title: "How-to: Validation",
        pages: &[
            "how-to-test-a-config-change-before-merge",
            "how-to-enforce-a-config-policy",
        ],
    },
    DocNavSection {
        title: "How-to: Application",
        pages: &[
            "how-to-load-config-from-a-git-repo-in-an-app",
            "how-to-keep-config-fresh-in-a-running-app",
        ],
    },
    DocNavSection {
        title: "How-to: Operations",
        pages: &[
            "how-to-investigate-why-a-value-was-selected",
            "how-to-diagnose-a-failing-workspace",
        ],
    },
    DocNavSection {
        title: "Examples",
        pages: &[
            "example-environment-specific-limits",
            "example-reviewed-account-class",
            "example-llm-agent-configuration",
            "example-tenant-specific-runtime-config",
            "example-incident-banner",
            "example-bucketed-rollout",
        ],
    },
    DocNavSection {
        title: "Reference",
        pages: &[
            "workspace-manifest-reference",
            "qualifier-reference",
            "variable-reference",
            "predicate-reference",
            "context-reference",
            "environment-reference",
            "value-types-reference",
            "source-uri-reference",
        ],
    },
    DocNavSection {
        title: "API",
        pages: &[
            "cli-reference",
            "rust-sdk-reference",
            "diagnostic-reference",
            "json-output-reference",
        ],
    },
];

pub fn get_page(id: &str) -> Result<&'static DocPage> {
    match lookup_page(id) {
        DocPageLookup::Found(page) => Ok(page),
        DocPageLookup::Ambiguous { query, pages } => Err(RototoError::new(format!(
            "documentation page `{query}` matched multiple pages: {}",
            page_ids(&pages)
        ))),
        DocPageLookup::NotFound { query, pages } => Err(RototoError::new(format!(
            "unknown documentation page `{query}`; available pages: {}",
            page_ids(&pages)
        ))),
    }
}

pub fn lookup_page(query: &str) -> DocPageLookup {
    let query = normalize_page_query(query);
    let slug_query = page_id_alias(&slug_query(&query));
    let text_query = text_query(&query);

    if let Some(page) = DOCS.iter().find(|page| page.id == slug_query) {
        return DocPageLookup::Found(page);
    }
    if let Some(page) = DOCS
        .iter()
        .find(|page| text_matches_exact(page.title, &text_query))
    {
        return DocPageLookup::Found(page);
    }

    let matches = best_page_matches(&slug_query, &text_query);
    match matches.len() {
        0 => DocPageLookup::NotFound {
            query,
            pages: DOCS.iter().collect(),
        },
        1 => DocPageLookup::Found(matches[0]),
        _ => DocPageLookup::Ambiguous {
            query,
            pages: matches,
        },
    }
}

fn normalize_page_query(query: &str) -> String {
    let query = query.trim();
    let query = query.strip_suffix(".html").unwrap_or(query);
    let query = query.strip_suffix("...").unwrap_or(query);
    match query {
        "" | "/" => "index".to_owned(),
        _ => query.to_ascii_lowercase(),
    }
}

fn slug_query(query: &str) -> String {
    query
        .trim_matches('/')
        .replace([' ', '_'], "-")
        .trim_matches('-')
        .to_owned()
}

fn page_id_alias(id: &str) -> String {
    match id {
        "model" => "rototo-model",
        "cli" => "cli-reference",
        "sdk" => "rust-sdk-reference",
        "diagnostics" => "diagnostic-reference",
        _ => id,
    }
    .to_owned()
}

fn text_query(query: &str) -> String {
    query.replace(['-', '_'], " ").trim().to_owned()
}

fn best_page_matches(slug_query: &str, text_query: &str) -> Vec<&'static DocPage> {
    let mut best_score = usize::MAX;
    let mut matches = Vec::new();

    for page in DOCS {
        let Some(score) = page_match_score(page, slug_query, text_query) else {
            continue;
        };
        if score < best_score {
            best_score = score;
            matches.clear();
        }
        if score == best_score {
            matches.push(page);
        }
    }

    matches
}

fn page_match_score(page: &DocPage, slug_query: &str, text_query: &str) -> Option<usize> {
    let title = page.title.to_ascii_lowercase();
    let list_title = page.list_title.to_ascii_lowercase();

    if page.id.starts_with(slug_query) {
        Some(0)
    } else if page.id.contains(slug_query) {
        Some(1)
    } else if title.starts_with(text_query) || list_title.starts_with(text_query) {
        Some(2)
    } else if title.contains(text_query) || list_title.contains(text_query) {
        Some(3)
    } else {
        None
    }
}

fn text_matches_exact(text: &str, query: &str) -> bool {
    text.to_ascii_lowercase() == query
}

fn page_ids(pages: &[&DocPage]) -> String {
    pages
        .iter()
        .map(|page| page.id)
        .collect::<Vec<_>>()
        .join(", ")
}