Replaces the cluster_config->port fallback (dead after task 1.2) with: --peers candidate match (unchanged, including single self-referencing entries for a custom single-node port) -> hardcoded XCE_CONTROLLER_DEFAULT_PORT (9990) when --peers was never passed at all. The --peers-given-but-local- name-absent case still returns -EINVAL and refuses to start, unchanged. Also folds in task 1.3's discovery (HashZArrayConfig/ResmgrConfig capnp structs left orphaned by tasks 1.1/1.2, breaking codegen) into requirements.md (new Req 2.5)/design.md/research.md/tasks.md, and fixes a design.md citation error (validate_peer_count -> xce_parse_peers) found during task 2.1's review.
18 KiB
Requirements Document
Introduction
ClusterConfig (src/schema/controller.capnp:711-733, generated as C struct cluster_config_t) is the capnp struct pushed cluster-wide via OP_upgradeConfig. It currently carries several fields that were bundled in without a principled reason: port, candidates (peer/standby list), vcpus, and the jobmgr/workermgr/qmgr/resmgr/xstrings performance-tuning fields. This spec removes those fields from the schema and the C code that reads them, so ClusterConfig contains only fields that are genuinely cluster-shared, per the classification in .kiro/steering/config-design.md.
Correction (2026-07-20, second review pass): candidates, vcpus, and the perf-tuning fields ARE already dead weight at runtime (verified — see Adjacent Expectations). port is NOT — cluster_open_listener (cluster.c:1216-1258) has a live fallback to cluster_config->port that is the sole port-sourcing mechanism for single-node deployments (no --peers flag is passed at all in single-node mode — main.c:293-294). Removing the port field without adding a replacement mechanism would break single-node controller startup. This spec now requires that replacement as part of the port removal (Requirement 1.7), not as an assumed-safe deletion.
This closes an assumption flagged in .kiro/specs/admin-config-tool/requirements.md (the "per-instance field preservation" risk) — not by verifying controller behavior, but by making the scenario structurally impossible: fields that don't exist in the schema can't be silently zeroed on a config push.
Boundary Context
-
In scope:
- Remove
port(@1) andcandidates(@2,List(MasterCandidate)) fromClusterConfig. - Remove
vcpus(@7) fromClusterConfig— already marked deprecated in the schema comment ("use --vcpus CLI flag; kept for config blob backward compatibility"); this is the schema revision that comment anticipated. - Remove
jobmgr(@13),workermgr(@14),qmgr(@15),resmgr(@16),xstrings(@17) fromClusterConfig. - Remove the C code that reads the above fields from
cluster_config_t: thecandidates/ncandidatesKV-mirror loop incluster_build_config(cluster.c) is dead-code removal (no behavior change); the port-fallback branch incluster_open_listener(cluster.c:1216-1258) is NOT dead code (see Introduction correction) and its removal requires a replacement mechanism per Requirement 1.7, not a bare deletion. Also remove the corresponding fields incluster_config_t(clusterconfig.h) and theSET_DEFAULT_IF_UNSET(c->port, ...)calls inmyconf.candconfig_repl.c(only after the replacement mechanism lands). - Regenerate capnp bindings after the schema change.
- Direct field removal (no deprecate-in-place transition, no field-number reservation) — the product has not reached 1.0, so capnp wire/on-disk compatibility for these fields is not a constraint (user decision, 2026-07-20).
- Remove
-
Out of scope:
heartbeatInterval(@3) andinactiveHeartbeatCount(@4) — these govern controller→worker liveness, not the HA/Raft election heartbeat (see Adjacent Expectations). Investigated and deliberately kept inClusterConfig(operational-uniformity override — see.kiro/steering/config-design.md); this spec does not touch them.fetchJobInterval(@5) andnetioTimeout(@6) — flagged by the priorsrc/docs/cluster-config-split-analysis.mdas candidates for the same treatment, but not yet decided in this pass. Left untouched; a candidate for a future iteration of this spec or a follow-up.clusterName(@0),resources,queues,ugroups,hgroups,hostConfigs,hproviders,limits— cluster-shared business config, unaffected.configGeneration(@20) — controller-assigned protocol state, not operator config; unaffected, not part of this spec's field-removal scope.- Adding a real HA/Raft election-heartbeat CLI parameter (
--heartbeat-interval-ms/--heartbeat-timeout-ms) to replace the currently-hardcodedHA_DEFAULT_HEARTBEAT_INTERVAL_MS/HA_DEFAULT_HEARTBEAT_TIMEOUT_MS— tracked as a separate roadmap item (.kiro/steering/roadmap.md, "Raft/HA heartbeat interval+timeout as CLI parameters"), not part of this spec. This spec does not add any newClusterConfigfield for HA heartbeat timing, and per Rule 0 inconfig-design.mdnever should. - Peer list replication protocol (joint-consensus membership changes) — a much larger future item noted in the prior analysis doc's Phase 5; not needed here since
candidatesis simply being deleted, not replaced with a new distributed mechanism (the live peer source,--peersCLI, already exists and is unaffected). entity-bearing-snapshots/config-upgrade-snapshot— unrelated concern (snapshot consistency at config-change boundaries); this spec has no dependency on either.
-
Adjacent expectations:
- Blast-radius finding (corrected 2026-07-20):
candidatesis fully dead inClusterConfigat runtime — the live peer list comes exclusively from the--peersCLI flag (parsed into a separate struct,myconf.peers_cfg), and the blob'scandidatesis read only into a debug-only KV mirror (cluster_build_config,cluster.c:851-885) with no other readers.jobmgr/workermgr/qmgr/resmgr/xstrings/vcpusare likewise dead — hardcoded constants incluster.c'sinit_controller_tunables()(cluster.c:1093-1108), with existing TODOs acknowledging the blob values are ignored;vcpusis sourced only from the separate CLI-setmyconf->vcpus, never from the blob.portis the exception:cluster_open_listener(cluster.c:1216-1258) falls back tocluster_config->portwhenever the local controller's name isn't matched in--peers— and single-node mode passes no--peersat all (main.c:293-294, "No --peers flag; running as single-node primary"), making this the sole port-sourcing path for that mode today. Also:xce_parse_peersonly WARNs (not rejects) on a--peerscandidate withport == 0(main.c:289-291), so a malformed--peersentry for the local node hits the same live fallback. Full analysis:src/docs/cluster-config-split-analysis.md(note: that doc's own effort/risk table calls this "Low risk... no fallback needed," which this spec's investigation found does not hold for single-node mode — treat the doc's file:line citations as reliable, its risk conclusion forportspecifically as superseded by this correction). - Two distinct "heartbeat" concepts (do not conflate when implementing): (1) the HA/Raft controller-to-controller election heartbeat (
ha->config.heartbeat_interval_ms, governs failover timing, currently hardcoded, noClusterConfigfield, out of scope here per Rule 0 inconfig-design.md); (2) the controller→worker liveness heartbeat (ClusterConfig.heartbeatInterval/inactiveHeartbeatCount, governs when a controller marks its own worker unavailable, stays inClusterConfig, out of scope here because it's unchanged, not because it's unrelated). admin-config-tooldependency:xceadmin config pushis soft-blocked on this spec landing (seeadmin-config-toolrequirements.md Adjacent Expectations) — not becausexceadmin's code depends on it, but because until these fields are removed, nobody has verified the controller preserves them correctly when a pushed config leaves them zero-valued.MasterCandidatecapnp struct: verified (grep, 2026-07-20) as referenced only byClusterConfig.candidatesand nowhere else incontroller.capnp— removal is required by Requirement 1.6, not an open design question.- Stale citation, verified and dropped: the prior analysis doc cites
testsuite/test.c:124,266for an unrelatedget_setting("port")(scheduler/worker connect-to-primary port) as a "don't touch this" landmine. That call no longer exists intest.cas of this spec's discovery pass — the citation has drifted. Two other test files DO reference a removed field directly:test_commit_barrier_rollout.c:1359andtest_config_repl.c:983(see Requirement 5.1). - Prior analysis document:
src/docs/cluster-config-split-analysis.md(2026-06-08) already scoped this exact change (its "Phase 1"), verified the zero-blast-radius claim, and listed the exact file:line change sites — treat it as a primary design input, not something to re-derive from scratch. Some line numbers in it have drifted; current locations are in this spec's discovery notes.
- Blast-radius finding (corrected 2026-07-20):
Requirements
Requirement 1: Remove Dead Peer/Listener Fields
Objective: As an XCE maintainer, I want port and candidates removed from ClusterConfig, so that the schema no longer implies these are cluster-shared values when the live peer list is already sourced from --peers.
Acceptance Criteria
- The
ClusterConfigcapnp struct shall not contain aportfield. - The
ClusterConfigcapnp struct shall not contain acandidatesfield. - When the controller opens its listener and its name is found in the
--peersCLI-parsed candidate list, the system shall determine the bind port from that list, exactly as it does today. - When the controller builds its internal debug/status config dump, the system shall not attempt to read
candidatesorncandidatesfromcluster_config_t(the field no longer exists). - The
cluster_config_tC struct (src/include/clusterconfig.h) shall not containport,ncandidates, or acandidates/master_candidate_t**member, matching the capnp struct. - The
MasterCandidatecapnp struct shall be removed fromcontroller.capnp(verified: referenced only byClusterConfig.candidates, nowhere else in the schema — its removal is not a design-phase open question, it's a direct consequence of Requirement 1.2'scandidatesremoval). - Where the controller's name is NOT found in
--peers(including single-node mode, where--peersis not passed at all), the system shall provide a way to determine the controller's listen port that does not depend on aClusterConfigfield. This spec does not mandate the specific mechanism (e.g., a new--portCLI flag, or requiring single-node mode to pass a self-referencing--peersentry) — selecting it is a design-phase decision — but Requirement 1.1's removal of theportfield SHALL NOT ship without this replacement in place, and single-node controller startup SHALL continue to work exactly as it does today once the replacement lands.
Requirement 2: Remove Dead Performance-Tuning Fields
Objective: As an XCE maintainer, I want the already-hardcoded performance-tuning fields removed from ClusterConfig, so that the schema doesn't advertise configurability that doesn't exist.
Acceptance Criteria
- The
ClusterConfigcapnp struct shall not containvcpus,jobmgr,workermgr,qmgr,resmgr, orxstringsfields. - While these fields are absent from the schema, the controller shall continue to use its existing hardcoded tunable values (
c->tunables.*defaults incluster.c) with no behavior change from before this spec. - When the controller starts, the system shall not attempt to read
vcpus,jobmgr,workermgr,qmgr,resmgr, orxstringsfromcluster_config_t(the fields no longer exist). - The
cluster_config_tC struct (src/include/clusterconfig.h) shall not containvcpus,jobmgr,workermgr,qmgr,resmgr, orxstringsmembers, matching the capnp struct. - The
HashZArrayConfigandResmgrConfigcapnp structs shall be removed fromcontroller.capnp(discovered during/kiro-impltask 2.1, 2026-07-20: verified via grep that these structs are referenced only by the removedjobmgr/workermgr/qmgr/xstringsfields and the removedresmgrfield respectively, nowhere else in the schema — their removal is a direct consequence of Criterion 2.1's field removal, the same class of cleanup as Requirement 1.6'sMasterCandidateremoval. Left in place, their$C.mapname(...)bindings reference C typedefs that Requirement 2.4's struct-member removal deletes, breaking the capnp-generated bindings' compile).
Requirement 3: Preserve Cluster-Shared Fields Unchanged
Objective: As an XCE operator, I want the fields that are genuinely cluster-shared to remain exactly as they are today, so that this schema cleanup doesn't regress any existing config-push behavior.
Acceptance Criteria
- The
ClusterConfigcapnp struct shall retainclusterName,heartbeatInterval,inactiveHeartbeatCount,fetchJobInterval,netioTimeout,resources,queues,ugroups,hgroups,hostConfigs,hproviders,limits, andconfigGenerationunchanged in type and semantics. Field numbers (@N) for these fields ARE expected to change (compacted to a contiguous0..12range): Cap'n Proto requires struct ordinals to be a gapless sequence, so removing 8 fields via direct deletion (Requirement 4.2 — not in-place reservation) necessarily renumbers every retained field declared after a removed one. This was verified empirically during/kiro-impl(2026-07-20): a schema with an ordinal gap fails to compile (error: Skipped ordinal @N. Ordinals must be sequential with no holes.). - Because retained fields are renumbered per 3.1, a
cluster.binbuilt before this spec is NOT expected to decode its retained fields correctly when pushed to a controller built after this spec — Cap'n Proto's field layout is ordinal-position-based, so renumbering shifts wire byte-offsets for every field after the first removed one, not just the removed fields' own bytes. This is an accepted compatibility break for this pre-1.0 product (see Requirement 4.1). Anycluster.binartifact that predates this spec — including the checked-inha/data/cluster.binused by/xce-deploy-haand/xce-test-ha— must be rebuilt against the new schema before being used with a post-spec controller (Requirement 5.3). - The controller→worker liveness behavior driven by
heartbeatInterval/inactiveHeartbeatCountshall be unaffected by this spec.
Requirement 4: Compatibility Posture
Objective: As an XCE maintainer, I want the compatibility expectations for this change stated explicitly, so implementation and review don't over- or under-build compatibility handling.
Acceptance Criteria
- Because this spec performs direct field removal via ordinal renumbering rather than in-place reservation (4.2), a
cluster.binbuilt before this spec is not binary-compatible with a controller built after this spec — not only are the removed fields' values (port,candidates,vcpus,jobmgr,workermgr,qmgr,resmgr,xstrings) discarded without error, warning, or migration, but retained-field values will not decode correctly if such a pre-spec blob is pushed as-is, since their wire byte-offsets shifted (see 3.1/3.2). This is accepted given the product has not reached 1.0; no backward-read guarantee is provided for any pre-speccluster.bin, and any such artifact — including the repo's own fixture — must be rebuilt against the new schema (Requirement 5.3). - The system shall not implement a deprecate-in-place transition (field-number reservation, dual-read-old-and-new) for the fields removed by this spec — direct removal (with the resulting renumbering of retained fields) is the chosen approach.
Requirement 5: Build and Test Verification
Objective: As an XCE maintainer, I want this schema change verified as behavior-preserving, so the "zero blast radius" claim from the prior analysis is proven, not assumed.
Acceptance Criteria
- When the controller AND its test suite are built after this spec's changes, the system shall compile without errors using the regenerated capnp bindings. This includes test code that directly constructs a
cluster_config_tliteral and sets a removed field — confirmed present attest_commit_barrier_rollout.c:1359(cfg.port = 9990;) andtest_config_repl.c:983(cfg.port = 9991;); both shall be updated to drop the now-nonexistent assignment. Any other direct struct-literal reference to a removed field found during implementation shall be updated the same way. - When the existing controller unit test suite is run after this spec's changes, the system shall show no new failures attributable to the removed fields.
- Because field-number renumbering breaks binary compatibility with any pre-spec
cluster.bin(Requirement 3.2/4.1), the system shall regenerate the checked-inha/data/cluster.binfixture against the new (post-renumbering) schema, preserving the semantic values of all retained fields from the pre-spec blob, so/xce-deploy-haand/xce-test-hacontinue to work for this and every future spec that relies on that fixture. - When a 3-node HA cluster is deployed and run through a config-push cycle after this spec's changes — using the regenerated
ha/data/cluster.binfrom Criterion 3 — the system shall behave identically to before this spec (peer discovery via--peers, listener binding, worker liveness tracking) — verified live, not just by unit tests, given this touchescluster.c/ha.c/main.c. - When a single-node controller (no
--peersflag) is started after this spec's changes, the system shall successfully bind its listener and operate correctly using Requirement 1.7's replacement port-sourcing mechanism — this is the scenario Finding #2 of this spec's review identified as the actual regression risk, and it shall be verified live, not assumed from the multi-node soak in Criterion 4.
Requirement 6 (Nice-to-Have): CLI-Configurable Performance Tunables
Objective: As an XCE operator running a high-memory or high-latency controller node, I want the previously-hardcoded hash-table-size tunables to become CLI-configurable, so I can tune an individual controller's performance without needing a schema field that was never wired up anyway.
Acceptance Criteria
- Where this nice-to-have is implemented, the system shall accept
--jobmgr-nhash,--workermgr-nhash,--qmgr-nhash,--resmgr-nhash,--xstrings-nhash(and corresponding-delta/-instvariants as applicable) CLI flags, following the sameOPT_*+struct arguments+myconf_tfield pattern already used for--vcpus. - Where a tunable CLI flag is not specified (or is 0), the system shall fall back to the existing hardcoded default for that tunable, with no behavior change from Requirement 2.