Repository navigation
feat(settings): importable settings model and validator, with in-memory validation #834
Description
Problem
A program that writes settings directories for a WaveHouse server has no way to check a folder against the server's own rules before handing it over. Examples: a control plane, a provisioning tool, a GitOps generator.
- Not importable. The only checking path is
settings.ValidateDir, which lives underinternal/. No other module can import it. - The CLI works only on disk.
wavehouse validateruns the same check, but only over a directory on disk, and reports human-readable text plus an exit code. A program has to write a temporary tree, spawn the binary at the right version, and scrape the output. - No HTTP dry run. The policy HTTP surface was removed in 0.2.0 (fix(policy): reject no-op rules, drop the policy HTTP surface #541).
POST /v1/ops/settings/reloadvalidates and adopts. - A writer must get it right first time. In a nested directory, a rejected folder fails closed with no fallback to its previous settings. One bad write takes that tenant out of service until the next good one.
A writer that re-implements the rules drifts from the server the first time they change. The only safe validator is the one the server runs, at the same version.
Proposed solution
-
Make the settings model and its validator importable. Move the parts that are pure data and pure checks out of
internal/into public packages in the same module. They are then versioned by the server's own release tags: a consumer pinned togithubproxy.fjygbaifeng.eu.org/Wave-RF/WaveHouse@v0.2.xvalidates with exactly the rules av0.2.xserver applies.- Public, from
internal/settings:- the document types (
Document,TenantConfigand its blocks,RolesFile,PipesFile); Files();Finding,SeverityandHasErrors;- the validator;
Seed().
- the document types (
- Their dependencies:
internal/policy,internal/pipes,internal/tenant(the tenant id grammar) andinternal/chsql. Today these import only the standard library,go-humanizeandyaml.v3. - Stays internal:
- the runtime half:
Registry,Store,Watch(and with itfsnotify), andWriteSeed; - the server's runtime dependencies (NATS, Pebble, Redis, the AWS SDK, the ClickHouse driver, OpenTelemetry), which the public packages must not import.
- the runtime half:
- Package paths are the maintainers' call (
settings/,policy/and so on at the module root, or underpkg/).
- Public, from
-
Validate in memory.
ValidateDiralready reads the four files into bytes (checkDir) and from then on checks only bytes. Split it at that seam and export the second half, for example:func ValidateFiles(files map[string][]byte) (*Document, []Finding)
An
fs.FSsignature works too.ValidateDirthen becomes "read the directory, thenValidateFiles".- Disk-only checks stay in
ValidateDir: the directory exists, no unexpected entries, no non-regular files, andconfig.json's permission bits. - Export the tenant id check, so a writer can validate a folder name before it creates the folder.
- One path.
wavehouse validate, boot and reload keep going through this single path; nothing gets a second validator.
- Disk-only checks stay in
-
Write with the same types.
- Export
Seed(), so a writer starts from the server's defaults instead of copying them. - Make sure marshaling the public types yields a document the validator accepts: every required key present, nothing the strict decoder rejects. A writer that builds the struct and marshals it then cannot emit a shape the server refuses.
- Export
-
Keep findings stable. Keep
FindingJSON-tagged and identical to thefindingsthe reload endpoint returns. The same list can then be shown to whoever edited the settings, whichever path produced it. -
Document it. Add a short "Validating from Go" section to the settings-directory page, with the version rule: validate with the package from the same tag as the server.
Acceptance criteria
- External import works. A module outside this repository can import the settings, policy and pipes packages at a release tag without importing anything under
internal/. - No runtime dependencies.
go list -depson those packages shows no server-runtime dependency. - Same findings in memory as on disk. For every existing validator fixture,
ValidateFileson the fixture's bytes returns the same findings asValidateDiron the fixture directory, apart from the disk-only checks. - Seed round-trips cleanly.
Seed()→ unmarshal into the public types → marshal →ValidateFilesreturns no errors. - No behaviour change. The CLI, boot and reload still share a single validation path.
Alternatives considered
- Shell out to
wavehouse validate. Works today, but every check needs the binary at the exact server version, a temporary directory, and parsing of human-readable output. There is no machine-readable mode. - An HTTP dry-run endpoint (e.g.
POST /v1/ops/settings/validate). No version skew, but it puts a running server on the path of every settings edit, and re-adds the kind of surface fix(policy): reject no-op rules, drop the policy HTTP surface #541 removed. - JSON Schemas (Validate settings files with published JSON Schemas #506). Complementary. Schemas help editors, but they can't express the cross-file checks (roles referenced by policies and pipes) or the warning tier, so a program still needs the Go validator. If Validate settings files with published JSON Schemas #506 lands, this public validator stays the single checking path.
Out of scope (possible follow-up)
Some checks need the table schema, such as a policy check that names a column the table lacks. Today they can't run without ClickHouse. A follow-up could expose them as a pure function over a column list, so a writer that already knows the schema can run them too.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
feat(settings): importable settings model and in-memory validator (#839)