|
9 | 9 | //! |
10 | 10 | //! ## Scope (this chunk) |
11 | 11 | //! |
12 | | -//! `meta`, `kernel`, `packages`, `services`, and `users` are emitted. |
13 | | -//! Unprobed `meta` fields (`timezone`, `arch_level`, `locale`, |
14 | | -//! `keymap`) emit conservative defaults that the operator hand-edits |
15 | | -//! after reconcile lands the file at |
16 | | -//! `<config_dir>/hosts/<host>.imported.ncl`. Subsequent chunks add the |
17 | | -//! config-file block. |
| 12 | +//! All [`DeclaredState`](pearlite_schema::DeclaredState) blocks |
| 13 | +//! relevant to a fresh import are emitted: `meta`, `kernel`, |
| 14 | +//! `packages`, `config`, `services`, and `users`. Unprobed `meta` |
| 15 | +//! fields (`timezone`, `arch_level`, `locale`, `keymap`) emit |
| 16 | +//! conservative defaults that the operator hand-edits after reconcile |
| 17 | +//! lands the file at `<config_dir>/hosts/<host>.imported.ncl`. |
| 18 | +//! |
| 19 | +//! ## Config-block caveat |
| 20 | +//! |
| 21 | +//! `config` is emitted as an empty array of tables. Probing populates |
| 22 | +//! [`ConfigFileInventory`](pearlite_schema::ConfigFileInventory) by |
| 23 | +//! stat-ing each *declared* target (see |
| 24 | +//! `pearlite_fs::inventory::probe_config_files`); a fresh-reconcile |
| 25 | +//! run has no declarations, so the inventory is empty by construction. |
| 26 | +//! Even when a partial inventory exists from prior declarations, the |
| 27 | +//! imported.ncl is a *new* draft for hand-curation rather than a |
| 28 | +//! round-trip of existing entries — re-emitting them with a |
| 29 | +//! placeholder `source` would override the operator's real config-repo |
| 30 | +//! paths. Operators add [`ConfigEntry`](pearlite_schema::ConfigEntry) |
| 31 | +//! tables manually during reconcile review. |
18 | 32 | //! |
19 | 33 | //! ## Users-block caveat |
20 | 34 | //! |
@@ -87,12 +101,17 @@ pub fn emit_host(probed: &ProbedState) -> String { |
87 | 101 | push_meta(&mut out, &probed.host.hostname); |
88 | 102 | push_kernel(&mut out, &probed.kernel.package); |
89 | 103 | push_packages(&mut out, probed.pacman.as_ref(), probed.cargo.as_ref()); |
| 104 | + push_config(&mut out); |
90 | 105 | push_services(&mut out, probed.services.as_ref()); |
91 | 106 | push_users(&mut out); |
92 | 107 | out.push_str("}\n"); |
93 | 108 | out |
94 | 109 | } |
95 | 110 |
|
| 111 | +fn push_config(out: &mut String) { |
| 112 | + out.push_str(" config = [],\n"); |
| 113 | +} |
| 114 | + |
96 | 115 | fn push_users(out: &mut String) { |
97 | 116 | out.push_str(" users = [],\n"); |
98 | 117 | } |
@@ -463,6 +482,27 @@ mod tests { |
463 | 482 | ); |
464 | 483 | } |
465 | 484 |
|
| 485 | + #[test] |
| 486 | + fn emit_config_emits_empty_array_placeholder() { |
| 487 | + // ConfigFileInventory is keyed on declared targets; a fresh |
| 488 | + // reconcile has none, so the block is always empty. |
| 489 | + let out = emit_host(&probed_with("forge", "linux-cachyos")); |
| 490 | + assert!(out.contains("config = [],")); |
| 491 | + } |
| 492 | + |
| 493 | + #[test] |
| 494 | + fn emit_config_appears_between_packages_and_services() { |
| 495 | + // DeclaredState field order: meta, kernel, packages, config, |
| 496 | + // services, users. The emitter mirrors it for stable |
| 497 | + // golden-fixture diffs. |
| 498 | + let out = emit_host(&probed_with("forge", "linux-cachyos")); |
| 499 | + let packages_at = out.find("packages = ").expect("packages block"); |
| 500 | + let config_at = out.find("config = ").expect("config block"); |
| 501 | + let services_at = out.find("services = ").expect("services block"); |
| 502 | + assert!(packages_at < config_at, "config must follow packages"); |
| 503 | + assert!(config_at < services_at, "config must precede services"); |
| 504 | + } |
| 505 | + |
466 | 506 | #[test] |
467 | 507 | fn emit_users_emits_empty_array_placeholder() { |
468 | 508 | // ProbedState carries no user inventory, so the emitter writes |
|
0 commit comments