Repository navigation
refactor(core): small layering roots for sealed values, authority checks, the health gate and ployz-decide - #4054
Merged
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
nick-potts
marked this pull request as ready for review
November 10, 2026 13:34
github-merge-queue
Bot
removed this pull request from the merge queue due to a conflict with the base branch
Nov 10, 2026
Config imported Sealed's type and sealed.rs imported it back. The type now lives beside SealedValue, and config re-exports it for existing callers.
The sorted-and-distinct list check had six copies across the signed bodies and the Mismatch loop had six more. Both now live in authority, so a bad list reads one problem and every check runs through first_mismatch.
Http and unconfigured Containers wait different deadlines in the gate than in a start's wait. The test holds today's verdicts before the gate's logic moves.
The gate carried its own copy of the grace constant, the Http monitor case and the default-deadline fallback. health_wait now owns each once, and the gate's NotConfigured fallback is a named Unconfigured argument.
A decision a model checks lives in one crate as a pure function that the model calls. clippy.toml refuses the clock and filesystem calls, so the purity is checked rather than hoped for.
nick-potts
force-pushed
the
t3/1eec8ee2
branch
from
November 10, 2026 14:21
e2b05a5 to
7fcd0fc
Compare
nick-potts
enabled auto-merge
November 10, 2026 14:33
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Nov 10, 2026
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.
Why
Untangle the
core/Rust workspace so pieces that change together live together, with behavior unchanged, so later agent changes read less, break less silently and debug faster. This is chunk D of batch 1: four small roots that later fixes build on.What changed
Four independent commits, plus a test commit that pins the dependency gate before its logic moves.
ClientSealedValuemoves fromconfig/service.rsintosealed.rs.configre-exports it, soployz_core::config::ClientSealedValuestill resolves.canonical::sorted_distinctandcanonical::sorted_by. Seven "build a (field, holds) table, return the firstMismatch" loops becomerefusal::first_mismatch, with the repeated labels asrefusal::field::*.ployz/src/deploy/exec/health.rsaskshealth_wait.rsfor its deadline. It usesHEALTHCHECK_RUNTIME_GRACEinstead of its ownRUNTIME_FAILURE_GRACE, callsmonitor_deadlinefor the Http case, and names its "also wait onNotConfigured" quirk asUnconfigured::UseDeadline.ployz-decide: no items yet, the rule in its//!, and aclippy.tomlthat refuses the clock and filesystem calls.ployz-verifytakes it as a dev-dependency.CODING_STANDARDS.mdgains the placement rule.Fixes and the problems they solve
T4-F13
Problem:
crates/ployz-core/src/config/service.rs:332ClientSealedValuelives in config, whilesealed.rs(mod sealer) imports it back, a config <-> sealed cycle.Decision log: "the type moves verbatim into
sealed.rs, the module that defines what Sealed values are and who wraps them;config/service.rsimports it, and apub useinconfigkeepscrate::config::ClientSealedValuefor existing callers. The edge becomes config -> sealed, one way. Move only; no body edits." Frozen risk: "ClientSealedValueserde form and any ts-rs export. Derives and serde attributes move verbatim and the ts-rs diff is the proof."Proof: the SDK declarations from
sdk-generatedare byte-identical on trunk and on this branch.R6-core-F1
Problems: the "sorted and distinct" deserialize check exists in four hand-written copies plus
services_by_nametwice (authority/session.rs:72); "build a table of (field, holds) and return the first Mismatch" is written six times with the same labels repeated (authority/session.rs:101).Decision log (a seventh table,
AutoDeployRule::verify_step, merged with #4047 after the log was written and moves too): "Inauthority/canonical.rsaddsorted_distinct... and a helper for the by-name case ... One error text, 'must be sorted and distinct'. ... Inauthority/refusal.rsaddpub(super) fn first_mismatch<const N: usize>(checks: [(&'static str, bool); N]) -> Result<(), AuthorityRefusal>and apub(super) mod field. The six sites named in the log (GrantBody::covers,RestartApproval::matches,deploy::verify,proposal::open_step,join::verify,ruleclaims) build their array and call it.AuthorityRefusal::Mismatch { field }and every label string stay byte-identical." Frozen risk: "None on the wire: the check runs at deserialize and accepts exactly the same lists (strictly ascending). Only the serde error text for a bad list changes (it is a Malformed refusal'sproblem)."T5-F4
Problem:
crates/ployz/src/deploy/exec/health.rs:74the dependency wait re-derives the deadline, the Http-monitor case and the 5s grace, and has drifted fromhealth_deadline_for.Decision log: "Make
health.rsuseHEALTHCHECK_RUNTIME_GRACEinstead of its ownRUNTIME_FAILURE_GRACE. Add onemonitor_deadlinehelper inhealth_wait.rsthathealth_deadline_forand the dependency wait both call, so the Http-monitor case lives once. The dependency gate's quirk (it also falls back onNotConfigured, whichhealth_deadline_fordoes not) stays and is named explicitly as anUnconfigured::{Settled, UseDeadline}argument at the one call site that needs it, so the divergence is visible and nothing changes behavior. No new single 'verdict and next wake' function: the two callers differ in what they do with the verdict." Risk: "Write a characterization test of today's dependency-gate verdicts before moving the logic."The test
dependency_gate_deadline_follows_the_healthcheck_kindis its own commit. It passed on the old code, then on the new:DisabledT8-F1
Problem:
crates/ployz-verify/Cargo.toml:1-14the only Ployz dev-dependency is ployz-core, so decisions inployzdandployzcannot be called, which is the root cause of the model copy findings.upgrade_attempt.rs:1each model is a hand copy linked by prose cites.Decision log: "new workspace crate
crates/ployz-decidewithployz-coreandserdeas its only dependencies. Itslib.rs//!states the rule: a decision a model checks is a free function or small enum machine over plain facts; time arrives asSystemTime/Durationparameters; notokio,std::fs,Instant::now, Docker or SQLite types. The crate shipsclippy.tomlwithdisallowed-methods... so the purity claim is mechanical, not a dependency-list hope.ployz-verifytakes it as a dev-dependency and[profile.dev.package.ployz-decide] opt-level = 3. Add one paragraph toCODING_STANDARDS.md."The crate has no items yet.
ployzd,ployzandployz-storedo not depend on it until the first decision moves in (T8-F2 onward).ployz-verify'slib.rssentence "calls the real code where it can" now lists what it calls.I checked that adding a crate touches no packaging or release script:
scripts/artifacts.pyandscripts/ci.pyderive closures fromcargo metadata(the crate is outside the SDK closure),.goreleaser.yamlnames packages explicitly, and theclitest group is--workspace --exclude ployzd --exclude ployz-store.DESIGN.md bets
health_wait.rsnow, not a copy in the gate.Mismatchlabel is byte-identical, so each refusal still names what the request failed to match.Scope
core/only. No wire or row format changes. No serialized type is added or changed (the ts-rs proof above coversClientSealedValue).Blast Radius
Does this alter Machine behavior? No. It touches no daemon (
ployzd), Docker, WireGuard, ZFS, move or deploy-wire code. One runtime path did change in shape: the dependency gate's deadline computation (T5-F4) now lives inployz-core. Its verdicts are pinned by the characterization test above and are unchanged. I'm skipping the Boat swarm because no behavior a Machine shows changes; the gate's verdict table, run before and after, is the evidence.Verification
cargo nextest run -p ployz-core -E 'test(authority) | test(sealed) | test(config::) | test(health_wait)': 187 passed.cargo nextest run -p ployz -E 'test(exec::tests::health) | test(container_health)': 33 passed, including the new characterization test.cargo check -p ployz-decide -p ployz-verify --testsand focusedcargo clippyonployz-core,ployz,ployz-decideandployz-verify: clean.cargo fmt --check: clean.cargo tree -p ployz-decide --depth 1:ployz-coreandserdeonly.disallowed-methodslist works: a probe callingInstant::now,SystemTime::now,fs::read_to_stringandFile::openproduced four clippy warnings (probe reverted).Residuals
problem, and the decision log accepts it. Nothing indocs/user, the tests or the dashboard pins the old text, and the browser always sends sorted lists.restart::ordered_machinespub(super), which this PR deletes. Whichever lands second pointsvolume.rsatcanonical::sorted_distinct. It already conflicts with trunk inployzd.ployz-decidedeclaresployz-coreandserde, andployz-verifyits dev-dependency, before anything uses them, as the decision log specifies.Unconfiguredflag is a mode argument by design (the log declines a merged verdict function). If the two callers ever need more differences, split it into two functions.