myxce/.kiro/specs/clusterconfig-schema-split/research.md
Rongsong Shen aecd8766f4 docs(admin-config-tool): clear stale OP_upgradeConfig dispatch blocker (bug-1468 already fixed)
admin-config-tool's spec.json blocked_on and tasks.md both still described
the OP_upgradeConfig dispatch-gate defect as an open blocker. It was fixed
2026-07-25 (bug-1468, commit 959b0cda) by config-upgrade-snapshot task 8.6,
which independently hit the same wall building a live OP_upgradeConfig
sender -- the fix's own comment in cluster.c already cross-references this
spec. The controller-side path has since been exercised live and
successfully many times via config-upgrade-snapshot's --config-upgrade
soak. All 23 admin-config-tool tasks are already [x]; this was pure
tracking-metadata debt, not a reopened implementation gap.

Also appends a cross-spec gap analysis (admin-config-tool x
config-upgrade-snapshot x clusterconfig-schema-split) to all three specs'
research.md, confirming clean schema integration between the latter two
and flagging the one remaining real gap: a new commit-timeout issue in
config-upgrade-snapshot task 8.6's force-elect step, surfaced now that
bug-1477 is fixed, not yet root-caused.
2026-07-28 14:13:06 +00:00

153 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research & Design Decisions
## Summary
- **Feature**: `clusterconfig-schema-split`
- **Discovery Scope**: Extension (existing system) — light discovery per `design-discovery-light.md`
- **Key Findings**:
- `candidates`, `vcpus`, and the five perf-tuning fields are genuinely dead in `ClusterConfig` at runtime (verified across every `.c` file that touches `cluster_config_t`).
- `port` is NOT dead — its removal (Requirement 1.1) needs a replacement mechanism (Requirement 1.7), which this design resolves as a new `--port` CLI flag, matching the existing `--vcpus` pattern exactly.
- No tooling in this repo (`deploy-ha.sh`, test scripts) currently starts a controller with zero peer-related flags in a way that would be affected either way — but the codebase's own `main.c:294` message ("No --peers flag; running as single-node primary") confirms the zero-`--peers` invocation is an intentional, first-class supported mode, not incidental. The design preserves it exactly rather than forcing a CLI-contract change.
## Research Log
### Requirement 1.7's port-sourcing replacement mechanism
- **Context**: `requirements.md` Requirement 1.7 deliberately left the replacement mechanism as an open design question after the requirements-phase review found `cluster_open_listener` (`cluster.c:1216-1258`) depends on `cluster_config->port` as the sole port source for single-node mode (no `--peers` flag passed at all, `main.c:293-294`).
- **Sources Consulted**: `src/controller/main.c` (CLI parsing, `--vcpus` precedent at lines 65-66/114-118/252, single-node branch at 293-295), `src/controller/cluster.c:1216-1258` (port resolution), `src/include/myconf.h` (existing CLI-derived field conventions), `src/controller/ha.h` (hardcoded-default naming convention, `HA_DEFAULT_*`), prior memory of this codebase (`--peers` with a single self-referencing candidate is a confirmed, already-used alternative single-node invocation — but no repo tooling actually relies on the zero-flag form being removed).
- **Findings**:
- No standalone `--port` CLI flag exists today — confirmed via grep (zero hits for `OPT_PORT`/`"port"` in `main.c`).
- `--vcpus` establishes the exact pattern to follow: `OPT_VCPUS` argp option → `struct arguments.vcpus` → validated → copied into `myconf.vcpus` (0 = unset sentinel, documented inline in `myconf.h:37`) → consumed at the point of use with a hardcoded fallback (`cluster.c:1320`, `myapp.c:129`: `(myconf->vcpus > 0) ? myconf->vcpus : 8`).
- `deploy-ha.sh` (the only repo-internal controller launcher found) always passes `--peers`; no repo script exercises the true zero-flag single-node path. This means changing that path's invocation contract would carry no *repo-internal* tooling risk — but it's still a documented, INFO-logged, intentionally-supported mode (`main.c:294`), not dead code, so removing it outright is a real behavior change to a supported feature, not cleanup.
- `xce_parse_peers`'s side effects (CA cert path population, identity string, `main.c:278-292`) only run inside the `if (arguments.peers_str)` branch — the zero-flag path currently skips them. Preserving the zero-flag path (rather than forcing a synthetic self-referencing `--peers` entry) avoids introducing any change to that unrelated cert/identity setup.
- **Implications**: Adding `--port` as a new CLI flag (rather than mandating `--peers` for all invocations) requires slightly more new code but changes zero existing invocation behavior and directly reuses a pattern already reviewed and shipped in this exact file. Given this spec's own history of an overconfident "zero blast radius" claim being wrong once already, the lower-risk option is preferred even at the cost of a few more lines.
## Architecture Pattern Evaluation
| Option | Description | Strengths | Risks / Limitations | Notes |
|--------|-------------|-----------|---------------------|-------|
| A: `--port` CLI flag (chosen) | New `OPT_PORT` flag, `myconf.port` field, hardcoded fallback constant when unset and not found in `--peers` | Zero invocation-UX change; exact precedent (`--vcpus`) to copy; isolated, low-risk diff | Slightly more new code than B | Selected — see Decision below |
| B: Mandate `--peers` everywhere (self-referencing entry for single-node) | Remove the zero-flag path; single-node must pass `--peers ctrl-1=host:port` | Single source of truth for port; reuses an already-proven 1-peer self-quorum path; less code | Removes a documented, intentionally-supported invocation mode; unverifiable whether anything outside this repo (docs, operator muscle memory) depends on the zero-flag form | Rejected — the removed mode is intentional/documented, not incidental, and this spec's mandate is schema cleanup, not an invocation-contract change |
## Design Decisions
### Decision: Add `--port` CLI flag as the replacement for `ClusterConfig.port`
- **Context**: Requirement 1.7 requires a non-`ClusterConfig` port-sourcing mechanism before `port` can be removed from the schema, without breaking single-node controller startup.
- **Alternatives Considered**: (A) new `--port` flag, hardcoded fallback default; (B) mandate `--peers` for all invocations including single-node.
- **Selected Approach**: (A). New `OPT_PORT` argp option → `struct arguments.port` (uint32_t, default 0) → copied to a new `myconf_t.port` field → `cluster_open_listener()`'s fallback branch reads `myconf->port` instead of `cluster_config->port`; if still 0 (flag not passed), falls back to a new hardcoded constant `XCE_CONTROLLER_DEFAULT_PORT = 9990` (matching today's exact `SET_DEFAULT_IF_UNSET(c->port, 0, 9990)` value byte-for-byte, so the default behavior is unchanged for anyone relying on it).
- **Rationale**: Exact structural precedent already exists and was already reviewed/shipped for `--vcpus`; zero changes to any existing invocation (scripted or manual); avoids introducing a new port-precedence edge case between two different flags (`--port` only matters when `--peers` doesn't supply a match, so precedence is unambiguous: `--peers` match > `--port` > hardcoded default).
- **Trade-offs**: A few more lines of new code than mandating `--peers` everywhere; two flags (`--peers`, `--port`) both nominally relate to "port" now, though only one (`--peers`) is meaningful once any peer is configured.
- **Follow-up**: None — this is fully specified for the tasks phase.
## Risks & Mitigations
- **Risk**: A future contributor reintroduces a `ClusterConfig`-sourced default for `port`/`candidates`/hash-tuning "for convenience" during an unrelated change, recreating the original problem. **Mitigation**: `.kiro/steering/config-design.md` now documents the general rule; this spec's removal is the concrete enforcement.
- **Risk**: The checked-in `ha/data/cluster.bin` might carry non-default values for the removed fields, silently masking a real backward-compat issue if the live-soak test (Requirement 5.3) doesn't actually inspect them first. **Mitigation**: Requirement 5.3 (and Testing Strategy below) mandates inspecting or synthesizing non-default legacy field values before relying on that test as proof.
- **Risk**: `test_commit_barrier_rollout.c:1359` / `test_config_repl.c:983` are the only *known* struct-literal hits, but a broader grep pattern might surface more during implementation. **Mitigation**: Requirement 5.1's catch-all clause; the build itself will fail loudly on any missed site (C struct field removal is a compile error, not a silent runtime gap).
## References
- `src/docs/cluster-config-split-analysis.md` — prior team analysis (2026-06-08), primary design input for the dead-field classification.
- `.kiro/steering/config-design.md` — general shared-vs-local config classification rule this spec enforces.
- `.kiro/specs/admin-config-tool/requirements.md` — downstream consumer whose "per-instance field preservation" risk this spec closes structurally.
## Design Review (`/kiro-validate-design`, 2026-07-20)
Initial GO/NO-GO: NO-GO, 3 issues found on a fresh critical pass of the first design draft. All fixed in `design.md` directly:
1. **Flow diagram dropped the zero-value fallback trigger.** Original diagram branched on "name found" (boolean); actual pre-spec code branches on `my_port == 0`, which also covers "found but that candidate's port field is malformed/zero" (`main.c:289-291` already WARNs on this input). As drafted, a matched-but-zero-port entry would have bound to literal port 0. Fixed: diagram and Service Interface contract now check the port value, not just match presence.
2. **Removing the `-EINVAL` path conflated two different scenarios.** Original design treated "`--peers` never passed" (true single-node) and "`--peers` passed but local name absent" (likely operator typo) identically — both silently falling to `--port`/hardcoded-default. User decision (interactive, during review): keep today's hard error for the second case; only the first gets the new fallback. Rationale: the project's fail-loud convention (`src/controller/CLAUDE.md` process-lifecycle invariant) — a silent wrong-default here would surface later as a confusing "peer unreachable" symptom instead of a clear startup error now. Fixed: System Flows, Service Interface, and Error Handling sections all updated to reflect the split.
3. **Requirement 6 missing from Boundary Commitments.** Present in File Structure Plan/Components/Traceability but not in "This Spec Owns" — mechanical inconsistency. Fixed: one bullet added.
Re-affirmed GO after fixes — no further review pass required (fixes were direct, not requiring a requirements-phase return).
## Design Revision (`/kiro-impl` kickoff, 2026-07-20)
**Reversed the Architecture Pattern Evaluation decision** (Option A → Option B) before implementation started, per direct user instruction at the start of `/kiro-impl`.
- **Original choice**: Option A, a new `--port` CLI flag + `myconf_t.port` field, mirroring `--vcpus`.
- **Revised choice**: Option B (previously rejected) — no new CLI flag. `cluster_open_listener`'s fallback becomes two-tier: `--peers` candidate match (unchanged) → hardcoded `XCE_CONTROLLER_DEFAULT_PORT` (9990) when `--peers` was never passed at all. Operators who want a non-default single-node port pass a single self-referencing `--peers <name>=<host>:<port>` entry (matching `-n`/`--myname`) — an **already-existing, already-supported** invocation mode (confirmed working: single-node primary via `--peers` omission OR a 1-peer `--peers` self-quorum entry are both live paths today, verified by grepping `cluster_open_listener`'s existing candidate-match loop against `c->myconf->name`).
- **Why the reversal holds up**: the original rejection of Option B was "removes a documented, intentionally-supported invocation mode" — but Option B as now scoped does NOT remove the zero-`--peers` mode; it only asks operators who need a *non-default port* in that mode to use the already-supported 1-peer `--peers` form instead of a brand-new flag. Zero new surface area, zero new plumbing (`main.c` argp wiring, `myconf_t.port` field, `myconf.vcpus`-style wiring all become unnecessary), and it reuses code that already exists and is already exercised.
- **Requirements impact**: none — Requirement 1.7 already listed "requiring single-node mode to pass a self-referencing `--peers` entry" as an explicit example alternative and left mechanism selection to the design phase.
- **Design impact**: `design.md`'s Overview, Goals, Boundary Commitments, Architecture diagram, File Structure Plan, Requirements Traceability, Components and Interfaces, Service Interface contract, and System Flows are all updated in place to reflect the two-tier fallback with no CLI Component for Requirement 1.7 (the CLI Component now exists only for the optional Requirement 6 tunables, reusing the pre-existing `--vcpus` pattern directly rather than a `--port` precedent).
- **Tasks impact**: `tasks.md`'s former Task 2 (`--port` CLI flag) is removed; all subsequent major tasks renumbered down by one.
- **Note**: `xce_parse_peers` also accepts a bare `host:port` candidate (no `name=` prefix), matched via `hostname` when no name is given — but `design.md`/`tasks.md` document the explicit `name=host:port` form as the canonical example for this replacement mechanism (clearer about intent than relying on hostname matching implicitly).
## Design Revision 2: Ordinal-Gap Discovery (`/kiro-impl` task 1.1, 2026-07-20)
The task 1.1 implementer, before touching the schema, found and empirically proved a contradiction between Requirement 3.1 (retained fields keep their literal original `@N` field number) and Requirement 4.2 (direct removal, no field-number reservation): Cap'n Proto requires struct ordinals to be a gapless `0..N-1` sequence — verified independently (`capnp compile` on a schema with a gap fails: `error: Skipped ordinal @1. Ordinals must be sequential with no holes.`). Deleting 8 fields from the middle of `ClusterConfig`'s 21 while freezing the 13 survivors' literal ordinals is not something any compiling schema can satisfy — you either renumber the survivors (breaks their wire byte-offsets for anything reading an old blob) or leave inert renamed placeholders at the old ordinals (which is field-number reservation, exactly what 4.2 forbids).
Neither `requirements.md` nor `design.md` had anticipated this; both were written assuming Cap'n Proto allows field deletion independent of ordinal freezing, which is not the case.
- **Decision (user, interactive)**: renumber the 13 survivors to a contiguous `0..12` range — literal direct removal, matching Requirement 4.2's exact text and the project's "still pre-release, no wire-compat obligation" posture. Requirement 3.1 amended to drop "field number" from its invariance clause (type/semantics only); Requirement 3.2 and 4.1 amended to state plainly that a `cluster.bin` built before this spec is not expected to decode correctly (not just the removed fields — every retained field after the first removed one shifts byte-offset).
- **Consequence discovered next**: the checked-in `ha/data/cluster.bin` (`deploy-ha.sh:33-36` copies it verbatim as every cluster's seed config) is itself a pre-spec artifact. Post-renumbering, its retained fields (`resources`, `queues`, `hostConfigs`, `hproviders`, `limits`, etc.) would silently decode to wrong bytes — breaking `/xce-deploy-ha` and `/xce-test-ha` for every future spec that depends on that fixture, not just this one's verification task. No existing tool rebuilds this blob (`xceadmin config build` isn't implemented yet — separate, unstarted spec).
- **Decision (user, interactive)**: add a task to regenerate `ha/data/cluster.bin` — decode its current retained-field values under the pre-spec schema, re-encode under the new schema with a small throwaway tool (written and discarded, not shipped), overwrite the fixture in place. New Requirement 5.3; old 5.3 (3-node soak) → 5.4, old 5.4 (single-node) → 5.5. `tasks.md` gained a new task 5.1 (regenerate fixture) ahead of the renumbered 3-node-soak task.
- **Rationale**: given the renumbering choice, the fixture WILL be silently wrong if left untouched — this is not optional cleanup, it's a repo-wide landmine for every HA spec's live verification, self-inflicted by this spec's own schema edit. Regenerating it once, in-scope, is far cheaper than every future spec independently rediscovering "why is deploy-ha producing garbage config."
- **Scope boundary held**: the throwaway encoding tool is explicitly NOT a permanent addition — building a real `cluster.bin` authoring tool is `admin-config-tool`'s job (`xceadmin config build`), not this spec's. This task only needs to reproduce ONE known blob's values under the new layout, not build general tooling.
## Design Revision 3: Orphaned HashZArrayConfig/ResmgrConfig Structs (`/kiro-impl` task 2.1, 2026-07-20)
The task 1.1 implementer deliberately left `HashZArrayConfig` and `ResmgrConfig` (the capnp struct types used by the now-removed `jobmgr`/`workermgr`/`qmgr`/`xstrings`/`resmgr` fields) in `controller.capnp`, correctly noting no requirement or task text asked for their removal — they compile fine as unreferenced-but-valid struct definitions on their own. Task 1.2 then removed their corresponding hand-written C typedefs (`hash_zarray_config_t`, `resmgr_config_t`) from `clusterconfig.h`, since nothing in `cluster_config_t` referenced them anymore. The task 2.1 implementer's partial syntax check surfaced the resulting break: capnp's C codegen still emits `HashZArrayConfig`/`ResmgrConfig` encode/decode prototypes referencing `hash_zarray_config_t`/`resmgr_config_t` via their `$C.mapname(...)` bindings — types that no longer exist anywhere in the tree.
- **Root cause**: this is the same class of gap Requirement 1.6 already closed for `MasterCandidate` (used only by the removed `candidates` field) — just missed for the tunables side during the original requirements pass, since `MasterCandidate` was called out explicitly by name while the tunables' supporting struct types were not.
- **Decision**: extend the same treatment — remove `HashZArrayConfig`/`ResmgrConfig` from `controller.capnp`. Verified via grep (same method as the original `MasterCandidate` verification) that neither struct is referenced anywhere else in the schema. New Requirement 2.5 added (mirrors 1.6's phrasing); design.md's File Structure Plan and Requirements Traceability updated; `tasks.md` gained task 1.3 (Schema Component, same boundary as 1.1) to close this before task 4's full build.
- **Why this wasn't caught in the requirements-phase discovery pass**: the original `cluster-config-split-analysis.md` and this spec's requirements-phase grep swept for the FIELDS being removed, not for supporting/nested struct TYPES those fields' capnp types depended on. A useful pattern for future field-removal specs: after identifying fields to remove, also check whether any of their types are struct types used nowhere else in the schema.
## Correction: Pre-Spec Single-Node Startup Was Already Broken (discovered during task 5.3's review, 2026-07-20)
Task 5.3's reviewer, verifying the live single-node scenarios, built and ran the actual pre-spec baseline (commit `94f50ce`, HEAD before this spec's first commit) for comparison — and found single-node startup (`xce_controller` with no `--peers`) was **already exiting with `-EINVAL`**, unconditionally, before this spec began. Root cause: `myapp_run()` (`src/controller/myapp.c`) calls `cluster_open_listener()` before the primary config (`cluster_config`) is loaded from disk, so the pre-spec fallback branch (`cluster_config->port`, only reachable when `cluster_config != NULL`) always hit its `else` branch. Logged as `bug-1457` (unrelated to this spec, not fixed as its own change — task 2.1's rewrite incidentally repaired it, verified live in task 5.3).
**This invalidates part of this spec's own premise, stated in the Introduction and Requirement 1.7's original correction**: "`cluster_open_listener`... has a live fallback to `cluster_config->port`... the sole port-sourcing mechanism for single-node deployments" and "single-node controller startup SHALL continue to work exactly as it does today." In reality, it did not work today (pre-spec) — for a reason entirely unrelated to `ClusterConfig`'s schema. The spec's actual behavior (task 2.1's hardcoded-default fallback, independent of `cluster_config` load state) is still correct and was still the right thing to build — it just fixed a real bug as a side effect, rather than merely "preserving" already-working behavior as originally framed. Not correcting the historical Introduction/Requirement 1.7 text itself (it accurately reflects what was believed and reviewed at design time); this note is the record of what live verification actually found.
---
## Cross-Spec Gap Analysis: admin-config-tool × config-upgrade-snapshot × clusterconfig-schema-split (2026-07-28)
### 1. Current State Investigation
All three specs touch the same controller-side config surface (`controller.capnp`'s `ClusterConfig`, `config_repl.c`'s `OP_upgradeConfig` handler, `cluster.c`'s dispatch gate) from different angles:
| Spec | Phase | Scope |
|---|---|---|
| `clusterconfig-schema-split` | `implementation-complete` | Remove per-instance/dead fields (`port`, `candidates`, perf-tuning) from `ClusterConfig` |
| `admin-config-tool` | `implementation-complete` (blocked_on stale — see §2) | `xceadmin` CLI: build/push `cluster.bin` against the controller |
| `config-upgrade-snapshot` | `tasks-generated`, 18/19 actionable tasks `[x]` | Snapshot the config alongside cluster state at every upgrade boundary; activation fencing |
**Verified current schema state** (`src/schema/controller.capnp:712-726`): `ClusterConfig` has exactly 13 fields (`@0`–`@12`: `clusterName` … `configGeneration`), matching `clusterconfig-schema-split`'s Requirement 3.1 target exactly. `MasterCandidate`/`HashZArrayConfig`/`ResmgrConfig` are gone (Requirements 1.6/2.5). `config-upgrade-snapshot`'s own schema additions (`HostAdminState`, `QueueAdminState`, lines 769/774) are **separate message types**, not fields on `ClusterConfig` itself — **no ordinal collision or renumbering conflict between the two specs' schema work.**
**Verified dispatch-gate state** (`src/controller/cluster.c:669-694`): the `OPCode_upgradeConfig` polarity-inversion bug that `admin-config-tool`'s `spec.json.blocked_on` and `tasks.md` (line 155) both cite as blocking real-cluster `config push` E2E validation is **already fixed** — commit `959b0cda` (bug-1468, landed 2026-07-25 as part of `config-upgrade-snapshot` task 8.6). The fix's own comment in `cluster.c` explicitly cross-references `admin-config-tool`: *"Independently diagnosed and documented as a blocking risk by the admin-config-tool spec (design.md 'Boundary Commitments' / 'Risks') before config-upgrade-snapshot task 8.6 needed a live OP_upgradeConfig sender and hit the same wall."*
### 2. Gap Found: `admin-config-tool`'s `blocked_on` is stale documentation, not a live blocker
`admin-config-tool/spec.json`'s `blocked_on` array still lists:
> "controller OP_upgradeConfig dispatch defect (cluster.c:667-679 vs ha.c:3926-3951) blocks real-cluster config push E2E validation"
This is the exact same defect as bug-1468, fixed 2026-07-25. Additionally, **the same controller-side `OP_upgradeConfig` path this blocker describes has since been exercised live, repeatedly, and successfully** by `config-upgrade-snapshot`'s own `--config-upgrade` soak (`.claude/skills/xce-test-ha/run-ha-soak.sh`) — dozens of live 3-node runs today alone, all showing correct dispatch, config replication, majority-ack, and `WAL_CONFIG_MARKER` commit. The only difference is the *client*: the soak drives the RPC via `xce_test`'s hand-rolled `upgradeconfig` command (built for `config-upgrade-snapshot` task 8.6, since no other live `OP_upgradeConfig` client existed at the time), not `xceadmin`'s Python `config push`. But the **controller-side code path is identical** for both clients — the dispatch gate doesn't distinguish caller identity.
- **Gap classification**: Missing (stale tracking data, not missing capability). `admin-config-tool`'s actual E2E validation task (referenced in tasks.md line 155) can very likely be marked done now, or re-run directly with `xceadmin config push` against a live cluster to close it out formally — the blocking condition itself no longer exists.
- **Recommendation for design/task phase**: Clear `admin-config-tool/spec.json`'s `blocked_on` entry (or mark it resolved with a dated note, matching this codebase's own convention — e.g. `clusterconfig-schema-split`'s already-resolved entries and `config-upgrade-snapshot`'s own dated Implementation Notes). If the E2E task was left unchecked pending this, run `xceadmin config push` against a real deployed cluster (`/xce-deploy-ha`) once to get first-hand `xceadmin`-specific confirmation (not just proof the shared controller path works) — the CLI's own TLS/redirect/framing code (Requirements 2–3) has never been proven against the real handler, only mock-tested (per tasks.md's own note).
### 3. Gap Found (residual, this session): `config-upgrade-snapshot` task 8.6 — new commit-timeout issue post-bug-1477
`config-upgrade-snapshot`'s own remaining task (8.6, live chaotic-upgrade soak) was blocked on bug-1472 → then bug-1477 (both now fixed and verified this session — see `.wolf/buglog.json`). Re-running `--config-upgrade` after the bug-1477 fix shows the soak's core assertions (checkpoint-identity, config-identity, host-admin-status survival — the things bug-1471/1472/1477 blocked) all **PASS** for the first time. But the same run then fails later, in the force-elect-rotation step, with repeated commit timeouts (`rc=-110`) on the primary — a step this soak scenario has **never previously reached** (every prior attempt failed earlier). This is very likely a fourth pre-existing, newly-exposed defect in the same family as bug-1471/1472/1474/1477 (surfaced by clearing the earlier blockers, not caused by them) — **not yet root-caused**, flagged for the next investigation pass. Task 8.6 remains open pending this.
- **Gap classification**: Unknown (root cause not yet investigated — flagged, not analyzed, per this skill's "defer deep research to design phase" principle).
- **Ownership**: Likely `UNCLEAR` until investigated — could be `ha-commit-durability`/`ha-term-safety`/raft-log-replication territory (commit-engine admission capacity or write-quorum under chaos), or a `config-upgrade-snapshot`-adjacent interaction. Follow this session's established routing discipline (bug-1471→1472→1474→1477 precedent): root-cause first, only fix in-place if narrowly scoped, otherwise route to the owning spec and keep task 8.6 blocked.
### 4. No gap found: `clusterconfig-schema-split` × `admin-config-tool` integration
`admin-config-tool/requirements.md`'s own Adjacent Expectations section already documents this dependency as "RESOLVED, landed" (commit `519e11c`), and the verified current schema state (§1 above) confirms it: `port`/`candidates`/perf-tuning fields are genuinely absent from `ClusterConfig`, so `xceadmin`'s Requirements 1.8–1.10 (never source these fields) have nothing to accidentally violate — the fields don't exist to source. No further action needed here.
### 5. No gap found: `clusterconfig-schema-split` × `config-upgrade-snapshot` schema coexistence
Verified directly (§1): the two specs' schema additions/removals touch disjoint parts of `controller.capnp` (`ClusterConfig`'s field list vs. new standalone `HostAdminState`/`QueueAdminState` structs). No ordinal renumbering conflict, no shared struct mutated by both specs in incompatible ways.
### Summary Table
| # | Finding | Classification | Effort | Risk | Action |
|---|---|---|---|---|---|
| 1 | `admin-config-tool` `blocked_on` stale (bug-1468 already fixed) | Missing (tracking, not capability) | S (update spec.json + one live `xceadmin config push` run) | Low | Clear blocker, optionally run one live E2E confirmation |
| 2 | `config-upgrade-snapshot` task 8.6: new commit-timeout past force-elect step | Unknown (not yet root-caused) | Unknown until investigated | Medium–High (commit/write-quorum path) | Dedicated debug pass before considering 8.6 closeable |
| 3 | `clusterconfig-schema-split` ↔ `admin-config-tool` | No gap | — | — | None |
| 4 | `clusterconfig-schema-split` ↔ `config-upgrade-snapshot` schema | No gap | — | — | None |
### Research Needed (carried forward)
- Root cause of the new post-bug-1477 commit-timeout (finding #2) — not investigated in this pass, flagged only.
**Resolved during this pass** (no longer open): `admin-config-tool/tasks.md` was audited directly — all 23 tasks are `[x]`, none gated on the stale blocker as an active checklist item. The E2E gap is purely `spec.json.blocked_on` + an informational `tasks.md` note (line 155), not an incomplete task. Confirms finding #1 is pure metadata cleanup, not a reopened implementation gap.