Skip to content

Commit dc30393

Browse files
feat(nickel): emit config block as empty-array placeholder (#64)
Closes the imported.ncl block coverage: every DeclaredState field the operator hand-edits during reconcile (`meta`, `kernel`, `packages`, `config`, `services`, `users`) now has an emitter slot in stable schema order. ConfigFileInventory is keyed on declared targets, so a fresh-reconcile run has nothing to emit; re-emitting partial inventory entries with a placeholder source would clobber operator-real config-repo paths, so we leave the array empty regardless. Block ordering now mirrors DeclaredState's serde field order (meta, kernel, packages, config, services, users) for stable golden-fixture diffs. Refs: PRD §11 (reconcile), Plan §6.1 (DeclaredState) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fa3733e commit dc30393

1 file changed

Lines changed: 46 additions & 6 deletions

File tree

crates/pearlite-nickel/src/emit.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@
99
//!
1010
//! ## Scope (this chunk)
1111
//!
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.
1832
//!
1933
//! ## Users-block caveat
2034
//!
@@ -87,12 +101,17 @@ pub fn emit_host(probed: &ProbedState) -> String {
87101
push_meta(&mut out, &probed.host.hostname);
88102
push_kernel(&mut out, &probed.kernel.package);
89103
push_packages(&mut out, probed.pacman.as_ref(), probed.cargo.as_ref());
104+
push_config(&mut out);
90105
push_services(&mut out, probed.services.as_ref());
91106
push_users(&mut out);
92107
out.push_str("}\n");
93108
out
94109
}
95110

111+
fn push_config(out: &mut String) {
112+
out.push_str(" config = [],\n");
113+
}
114+
96115
fn push_users(out: &mut String) {
97116
out.push_str(" users = [],\n");
98117
}
@@ -463,6 +482,27 @@ mod tests {
463482
);
464483
}
465484

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+
466506
#[test]
467507
fn emit_users_emits_empty_array_placeholder() {
468508
// ProbedState carries no user inventory, so the emitter writes

0 commit comments

Comments
 (0)