docs(controller-status): add Requirement 12 delta (primary election term/time)
Adds requirements, design, research, and tasks for reporting the term and wall-clock time the current primary was elected, including standby-side learned fidelity (mirroring the Req 10 pattern), following the existing controller-status spec's delta convention (Req 10/11). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
63de947305
commit
7556856e5c
5 changed files with 245 additions and 8 deletions
|
|
@ -89,6 +89,54 @@
|
|||
- _Requirements: 10.1, 10.3, 11.2, 11.3_
|
||||
- _Depends: 6.2, 6.3_
|
||||
|
||||
## 7. Delta: primary election term + time (Req 12)
|
||||
> Sequential — the election-time stamp touches the same HA promotion/heartbeat control flow as tasks 2.2/6.2 (ha.c); no `(P)`. Read-only reporting only: this task must not change how elections are conducted, how term advances, or how/when role transitions to primary — every write here is an additional observational stamp at an already-decided instant. `electedTerm` needs no new field (design.md Locked Decision 1); it is a CLI-side derivation added in 7.5, not new state.
|
||||
|
||||
- [ ] 7.1 Add the election-time schema fields and controller state
|
||||
- Add a wall-clock election-time field to the status reply payload (next free ordinal, append-only) and to the heartbeat message the primary already sends to standbys (next free ordinal).
|
||||
- Add the corresponding piece of HA state that holds the currently-known primary's election time, alongside the existing learned-committed-position state.
|
||||
- Add the matching fields to the hand-authored C structs backing these two payloads (this schema's `$C.mapname` types are NOT auto-generated — the generated codec references hand-written typedefs in `typemap.h`, per tasks 1.1/2.1/6.1's established convention) and verify each new field field-for-field against the regenerated codec.
|
||||
- Observable: the build regenerates the reply and heartbeat codecs clean with the new fields present, the hand-authored C structs match the codec field-for-field, a codec round-trip carries each new field's value unchanged, and the new HA state field exists and defaults to zero/unknown before any election.
|
||||
- _Requirements: 12.1_
|
||||
- _Boundary: schema + typemap + ha_state_t_
|
||||
|
||||
- [ ] 7.2 Stamp the election time when a node becomes primary
|
||||
- TDD (red first): a promotion via the non-election-protocol path and a promotion via the leader-election path both record a non-zero election-time at the moment the node commits to the primary role; a promotion attempt that is refused (fenced, ineligible, or otherwise rejected) records NO election-time and leaves any prior value untouched.
|
||||
- Stamp the wall-clock election time exactly once, at the point each promotion path irrevocably commits to the primary role — after any eligibility/refusal checks that path performs, never before.
|
||||
- Observable: the new tests go red → green; a refused promotion attempt (either path) leaves the election-time state unchanged, proving the stamp is not placed before the refusal checks.
|
||||
- _Requirements: 12.1, 12.5_
|
||||
- _Boundary: promotion (ha_do_promote, raft_cb_become_leader)_
|
||||
- _Depends: 7.1_
|
||||
|
||||
- [ ] 7.3 Carry the primary's election time to standbys over the heartbeat
|
||||
- TDD (red first): a standby that processes a heartbeat carrying the primary's election time stores it as its own learned election-time and leaves every other learned/produced HA field (committed position, role, term, applied position) untouched (reconcile-free).
|
||||
- The primary includes its own election time in every heartbeat it sends; a standby that receives a heartbeat stores the carried election time as its learned value.
|
||||
- Observable: the reconcile-free test passes — only the learned election-time changes, nothing else moves; a live/loopback heartbeat round-trip shows the standby's learned election-time matching the primary's.
|
||||
- _Requirements: 12.2_
|
||||
- _Boundary: heartbeat (send + receive)_
|
||||
- _Depends: 7.1_
|
||||
|
||||
- [ ] 7.4 Source the election time in the status handler
|
||||
- TDD (red first): the status handler reports a non-zero election-time equal to the responder's own learned value (self-stamped on a primary, heartbeat-learned on a standby); a standby seeded with a learned election-time (simulating an already-processed heartbeat) reports that learned value, not zero; the handler still mutates no state.
|
||||
- Read the learned election-time into the status reply unconditionally (no role-based branching, no "unknown" gating in the handler itself — that display decision belongs to the client command in 7.5, mirroring how the handler already reports raw term unconditionally regardless of whether a primary is currently known).
|
||||
- Observable: the new unit tests go red → green; a standby seeded with a learned election-time reports that value instead of zero; the no-mutation assertion holds.
|
||||
- _Requirements: 12.1, 12.2, 12.5_
|
||||
- _Boundary: ctrlstatus_fill_
|
||||
- _Depends: 7.1_
|
||||
|
||||
- [ ] 7.5 Render the election term and time in the `ctrlstatus` client command
|
||||
- Print the primary's election term and election time next to the primary's identity in the client command's output, rendering the election time on the same clock/format already used for the responder snapshot timestamp; render both as "unknown" whenever the primary identity itself is reported as unknown.
|
||||
- Observable: piping `ctrlstatus` through the client prints the election term and a log-comparable election-time string alongside the existing fields when a primary is known, and prints "unknown" for both when it is not.
|
||||
- _Requirements: 12.1, 12.3, 12.4, 12.6_
|
||||
- _Boundary: cmd_ctrlstatus_
|
||||
- _Depends: 7.4_
|
||||
|
||||
- [ ] 7.6 End-to-end validation on a live cluster
|
||||
- Run against a live 3-node cluster: query the primary right after a fresh election and confirm its printed election term matches its current term and its election time is close to "now"; query a standby and confirm it reports the SAME election term and election time as the primary (not "unknown", not stale) once at least one heartbeat has been exchanged; kill the primary, wait for a new election, and confirm the surviving standby's reported election term/time updates to the new primary's values within about one heartbeat interval.
|
||||
- Observable: the live run shows matching election term/time between the primary and a standby, and shows the standby's values converge to the new primary's after a kill within one heartbeat interval; manual environment prerequisite (live cluster), same status as the other live-E2E tasks (5.1, 6.4).
|
||||
- _Requirements: 12.1, 12.2, 12.4, 12.6_
|
||||
- _Depends: 7.2, 7.3, 7.4, 7.5_
|
||||
|
||||
## Implementation Notes (5.1 E2E outcome)
|
||||
- 5.1 ran live on a 3-node `/xce-deploy-ha`-style cluster (ext2/3 fs). PASSED in-scope: `ctrlstatus` shows the primary self-reporting (`Primary: ctrl-1` on the primary), standbys honestly report `Primary: unknown` with `answered by ctrl-2 (standby)` (the dispatch-gate lets standbys answer — Req 7); peers + count correct; `configChecksum` = the real 64-hex SHA-256 (matched the deploy tag prefix) and is IDENTICAL across all 3 nodes with identical config (Req 5.3); `configVersion`==`committedConfigVersion`; authoritative `wait-primary` names the primary in <1s on a serveable cluster (Req 9.1/9.2).
|
||||
- 5.1 FOUND + FIXED a real 2.1 bug: `ha->primary_host` is VESTIGIAL (never written anywhere in the controller), and `voted_for_name` is the last-vote (often self), so neither names the current primary off-primary. Corrected: PRIMARY self-reports its own name; STANDBY/ELECTING/INIT report empty → "unknown" (the authoritative client path reaches the real primary by rotation). Commit ade0081.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue