milestone 20: declarative configuration for iac

This commit is contained in:
2026-08-11 23:31:40 +02:00
parent 2f29121e27
commit d76afc147a
74 changed files with 6722 additions and 1949 deletions
+27 -10
View File
@@ -13,17 +13,26 @@ const validate = @import("validate.zig");
/// `ValidateError` enters as a whole set rather than variant by variant, so a
/// variant added to the validator cannot silently fall through to exit 1. The
/// four extras are the configuration faults raised outside the validator: the
/// ZON reader (`ParseZon`), the seed-file size limit (`ConfigTooLarge`), the
/// composition root's upstream build (`NoUsableUpstreams`) and its certificate
/// load (`BadCertificate`).
/// five extras are the configuration faults raised outside the validator: the
/// ZON reader (`ParseZon`), the file size limit (`ConfigTooLarge`), the managed
/// file the operator named and this process cannot open
/// (`ManagedConfigUnreadable`, milestone-20 ruling 2), the composition root's
/// upstream build (`NoUsableUpstreams`) and its certificate load
/// (`BadCertificate`).
///
/// Not here on purpose: `error.DatabaseNotEmpty`, which reports the state of
/// the database rather than the content of a file, and is the one config-shaped
/// exit 2 `cli` decides for itself.
/// `ManagedConfigUnreadable` is the only place a missing or unreadable path is a
/// configuration fault, and it is deliberately not `FileNotFound` itself: the
/// operator named that path on the command line, so it is theirs to fix, while a
/// missing file anywhere else stays a runtime failure. `config/loader.zig` owns
/// the conversion and the closed set of open errors that qualify.
///
/// Not here on purpose: `error.DestructiveImport`, which reports what an import
/// would do to the database rather than the content of a file, and is the one
/// config-shaped exit 2 `cli` decides for itself.
const ConfigFault = validate.ValidateError || error{
ParseZon,
ConfigTooLarge,
ManagedConfigUnreadable,
NoUsableUpstreams,
BadCertificate,
};
@@ -94,6 +103,14 @@ test "the faults raised outside the validator are configuration faults" {
try testing.expect(isConfigFault(error.BadCertificate));
}
test "a managed file the operator named and this process cannot open is exit 2" {
// Ruling 2. The general rule below still holds — a bare `FileNotFound` is a
// runtime failure — and this is the one converted form, produced only by
// `config/loader.zig` for a path `--config` named.
try testing.expect(isConfigFault(error.ManagedConfigUnreadable));
try testing.expect(!isConfigFault(error.FileNotFound));
}
test "the seed-file errors that used to exit 1 from run are configuration faults" {
// D1 verbatim: these three reached `run` from a rejected seed file and were
// classified as runtime failures.
@@ -107,9 +124,9 @@ test "a runtime failure is not a configuration fault" {
try testing.expect(!isConfigFault(error.AccessDenied));
try testing.expect(!isConfigFault(error.FileNotFound));
try testing.expect(!isConfigFault(error.AddressInUse));
// A state conflict, not a bad file: `import` refuses to overwrite a
// configured database and decides that exit code itself.
try testing.expect(!isConfigFault(error.DatabaseNotEmpty));
// A verdict on the diff, not on the file: `import` refuses a run that would
// delete rows and decides that exit code itself.
try testing.expect(!isConfigFault(error.DestructiveImport));
// Only ever a warning, so it never reaches an exit code by this route.
try testing.expect(!isConfigFault(error.SourceInNoGroup));
}