feat(hfs): HFS_CLUSTER fail-fast validation and the job-store selector (Phase 0.2) - #906
Open
aacruzgon wants to merge 1 commit into
Open
feat(hfs): HFS_CLUSTER fail-fast validation and the job-store selector (Phase 0.2)#906aacruzgon wants to merge 1 commit into
aacruzgon wants to merge 1 commit into
Conversation
…ector Nothing stopped an operator from running N hfs instances on a SQLite primary, node-local bulk output, a file audit sink, or a MongoDB/S3 primary whose bulk-export job store is a node-local SQLite sidecar — each of which silently loses data or returns 404s once requests spread across instances. `HFS_CLUSTER=true` now declares the process one of N instances and the binary refuses to boot on those configurations, collecting every violation into one `Configuration error:` block that names the variable and the safe value (F1 sqlite primary, F2 local-fs bulk output, F3 sidecar job store under mongodb/s3, F4 file audit, explicit memory job store). Configurations that merely run per-instance today — SQL-on-FHIR $sql-export job state, and an `fs` export sink that needs a shared directory — warn instead. The validator lives in crates/hfs because the rest and audit configs only meet there, and is a pure function over a `ClusterConfigView` so the table is testable without env mutation. `HFS_JOB_STORE_BACKEND` (memory | database; unset → database under cluster) is parsed and validated ahead of the subsystems that will consume it. Tests: six validator tests written red against a stub (four failed) and green with the implementation; two config tests for the selector's default flip and invalid-value rejection; manual boot verified (five refusals + exit 1 under cluster, health 200 without it, parse-time rejection of an invalid selector).
This was referenced Sep 2, 2026
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 0.2 of the cluster-capable-state rebuild (discussion #223). Adds the
HFS_CLUSTERmaster switch and a fail-fast validator: underHFS_CLUSTER=truethe server refuses to boot on configurations that cannot run as one of N instances, printing oneConfiguration error:line per violation that names the variable and the safe value, and logs a warning for the configurations that merely run per-instance today. Also parses and validatesHFS_JOB_STORE_BACKENDahead of the subsystems that will consume it.Independent of #905 (no shared files); branched from
main.Changes
crates/rest/src/config.rs—cluster: bool(HFS_CLUSTER, default false) andjob_store_backend: String(HFS_JOB_STORE_BACKEND, default unset) onServerConfig;JobStoreBackend { Memory, Database }withjob_store_backend_mode()(unset →Databaseunder cluster, elseMemory; explicit values win;dbaccepted). An invalid value is avalidate()error naming the variable, so it fails at parse time like every other config error.crates/hfs/src/cluster.rs(new) —ClusterConfigViewassembled from the rest and audit configs (they only meet in the binary), andvalidate_cluster_config→ClusterVerdict { errors, warnings }. A pure function, so the table is unit-tested without env mutation. Refusals underHFS_CLUSTER=true:HFS_STORAGE_BACKEND)local-fsbulk export / bulk submit output (each only when that subsystem is enabled)main.rs, worst case a per-process temp fileHFS_AUDIT_BACKEND=fileHFS_JOB_STORE_BACKEND=memoryWarnings (boot proceeds): SQL-on-FHIR
$sql-exportjob state is per-instance until a database controller exists, andHFS_EXPORT_SINK=fsneeds a shared directory. A shared NFS export directory is a legitimate deployment, so the sink is a warning, not a refusal.crates/hfs/src/main.rs— wires the check right after the storage mode is resolved and before any subsystem starts; warnings viawarn!, errors via the existingConfiguration error:+exit(1)convention.Skills —
run-hfs-servergains a Clustering section (both.claude/skillsand.agents/skills).Testing
Red first: the six validator tests were written against a stub returning an empty verdict; four failed (
cluster_on_refuses_each_unsafe_dimension,cluster_on_collects_all_violations_at_once,cluster_on_accepts_a_fully_shared_configuration,fs_export_sink_warns_not_refuses), the two "accepts" cases passed trivially. All six pass with the implementation.cargo test -p helios-hfs --no-default-features --features R4,sqlite,postgres cluster::— 6 passed (refusal table of 7 cases, collect-all = 5, disabled subsystems exempt, cluster-off accepts every single-instance default, fs sink warns).cargo test -p helios-rest --lib config::tests— 61 passed, including the two new job-store tests.HFS_CLUSTER=true HFS_STORAGE_BACKEND=sqlite HFS_JOB_STORE_BACKEND=memory HFS_AUDIT_BACKEND=file …prints fiveConfiguration error:lines and exits 1; the same config withoutHFS_CLUSTERboots and answers/health200 with no cluster output;HFS_JOB_STORE_BACKEND=kafkais rejected at parse time.cargo fmt --all; CI-exact clippy clean.Notes
HFS_CLUSTER=trueis set.HFS_JOB_STORE_BACKENDis parsed and validated but not yet consumed; the unified job store (Phase 1) reads it.