From 32bf324a1e7e542046e770d3592dd2c2ea0d31d1 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Sun, 16 Aug 2026 18:06:36 +0200 Subject: [PATCH] CB-602: guard against a config key that never reaches the example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BridgedConfig.KNOWN_TOP_LEVEL_KEYS is now package-private so a test can assert every key the parser accepts appears in bridged.example.yaml — live or commented-out, since the file is gitignored and the example is the only committed description of the config schema. The existing tests only checked the example->code direction; this adds code->example. --- .../ltms/bridged/config/BridgedConfig.java | 6 +- .../bridged/config/BridgedConfigTest.java | 74 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java b/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java index 3140e68..0b258fd 100644 --- a/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java +++ b/bridged/src/main/java/dev/ltms/bridged/config/BridgedConfig.java @@ -967,8 +967,12 @@ public record BridgedConfig( /** * Top-level keys this version understands. Used only to warn about the rest — see * {@link #warnUnknownTopLevelKeys}. Keep in step with the record components. + * + *

Package-private (not {@code private}) so a test can assert every key here is documented in + * {@code bridged.example.yaml} — the only committed description of the config schema, since + * {@code bridged.yaml} itself is gitignored. */ - private static final Set KNOWN_TOP_LEVEL_KEYS = Set.of( + static final Set KNOWN_TOP_LEVEL_KEYS = Set.of( "bind", "herdrSocket", "profiles", "guard", "worktreeRoot", "lifecycle", "spawnReadyTimeoutMs", "spawnReadyPollMs", "broker", "primary", "fleet", "leadHeartbeat", "health", "placement", "auth", "configReload", "quarantineCooldownSeconds"); diff --git a/bridged/src/test/java/dev/ltms/bridged/config/BridgedConfigTest.java b/bridged/src/test/java/dev/ltms/bridged/config/BridgedConfigTest.java index 0cda820..dc91af1 100644 --- a/bridged/src/test/java/dev/ltms/bridged/config/BridgedConfigTest.java +++ b/bridged/src/test/java/dev/ltms/bridged/config/BridgedConfigTest.java @@ -10,6 +10,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.*; @@ -1189,6 +1190,79 @@ class BridgedConfigTest { assertEquals(5, cfg.leadHeartbeat().quietNudgeCap()); } + /** + * A top-level key {@code BridgedConfig} reads but that appears nowhere in + * {@code bridged.example.yaml} — live or commented — is invisible drift: {@code bridged.yaml} + * is gitignored, so the example is the ONLY committed description of the config schema, and + * neither {@link #shippedExampleConfigParses} (example → code: does the example still parse) + * nor {@link #everyOptionalKnobDocumentedInTheExampleBinds} (a hand-maintained list of keys + * that must bind) can catch a brand-new key nobody added to either. + * + *

This test compares the OTHER direction: every key in {@link BridgedConfig#KNOWN_TOP_LEVEL_KEYS} + * (the parser's own accepted set, which backs the unknown-key WARN) must appear as a top-level + * key in the example text, live or commented-out — see {@link #topLevelKeyDocumented}. + */ + @Test + void everyKnownTopLevelKeyIsDocumentedInTheExample() throws Exception { + Path example = Path.of("bridged.example.yaml"); + assertTrue(Files.exists(example), "bridged.example.yaml must ship next to the pom"); + String text = Files.readString(example); + + List undocumented = BridgedConfig.KNOWN_TOP_LEVEL_KEYS.stream() + .filter(key -> !topLevelKeyDocumented(text, key)) + .sorted() + .toList(); + + assertTrue(undocumented.isEmpty(), () -> "key(s) " + undocumented + + " are read by BridgedConfig but appear nowhere in bridged.example.yaml — " + + "document each one there, commented out if optional. bridged.yaml is " + + "gitignored, so this file is the only committed description of the config " + + "schema an operator or a worker can see."); + } + + /** + * Most of {@code bridged.example.yaml} is deliberately commented out — optional sections are + * documented as commented blocks so the shipped file stays a working minimal config. A key + * documented ONLY as a comment must still count as documented; parsing the file as YAML and + * reading its live key set (as an earlier attempt at this guard did) gets this wrong, because + * every commented section then looks entirely absent. + */ + @Test + void commentedOnlyTopLevelKeyCountsAsDocumented() { + String yaml = """ + bind: + port: 8765 + # broker: + # uri: amqp://guest:guest@127.0.0.1:5672 + """; + assertTrue(topLevelKeyDocumented(yaml, "broker"), + "a key documented only inside a commented-out block must still count as documented"); + } + + /** A key that appears in neither a live nor a commented top-level line must NOT count. */ + @Test + void absentTopLevelKeyIsNotDocumented() { + String yaml = """ + bind: + port: 8765 + """; + assertFalse(topLevelKeyDocumented(yaml, "broker"), + "a key mentioned nowhere in the example must not be reported as documented"); + } + + /** + * True when {@code key} appears as a top-level YAML key in {@code yaml} — either live + * ({@code key:} at column 0) or commented out ({@code # key:}, also at column 0, with only + * whitespace between the {@code #} and the key). Anchoring on column 0 is what keeps this a + * top-level check: an indented occurrence (a nested field, or prose inside a comment that + * happens to end in a colon) never matches, because {@code ^} requires the key's own first + * character — or the sole leading {@code #} — to sit at the very start of the line. + */ + private static boolean topLevelKeyDocumented(String yaml, String key) { + Pattern p = Pattern.compile("(?m)^(?:#\\s*)?" + Pattern.quote(key) + ":"); + return p.matcher(yaml).find(); + } + @Test void placementDefaultsToFixedForExistingConfigs(@TempDir Path dir) throws Exception { Path f = dir.resolve("no-placement.yaml");