feat(cardano): committee, constitution and governance singleton state - #1130
feat(cardano): committee, constitution and governance singleton state#1130scarmuega wants to merge 4 commits into
Conversation
… state Phase 3 of the Conway governance work: introduce the `GovState` singleton entity (ns "gov") carrying the enacted constitution, committee, committee hot-key authorization histories, per-purpose governance roots, and the dormant-epoch counter; handle the two committee certificates; and move DRep expiry to epoch-based, Haskell-style bookkeeping on `DRepState`. - `GovState` seeded from the Conway genesis at the Chang era boundary (`GovGenesisInit` in ESTART finalize) or at bootstrap for networks that force-start in Conway; committee auths tracked as slot-stamped histories so boundary tallies can read them as-of a past slot. - `AuthCommitteeHot` / `ResignCommitteeCold` certificates now emit `CommitteeAuth` / `CommitteeResign` deltas (valid txs only). - `DRepState.expiry` (new Option field at CBOR index 8): stored without dormancy credit, refreshed on registration (with the PV9 bootstrap exception), update certs, and DRep votes; snapshot-safe via an epoch-rotated `(current, updated_in, prev)` record. - Dormancy release (research §3.3.1): the first proposal after a dormant stretch fans out `DRepDormancyRelease` over the dreps namespace and resets the counter (`GovDormancyReset`), matching the Haskell CERTS rule ordering (release before certs/votes of the same tx). - `should_expire_drep` at EWRAP now compares the stored epoch expiry (+ dormancy credit) exactly as the ledger does, falling back to the legacy slot heuristic for pre-upgrade rows. Entity changes are backward-compatible `Option` additions at unused CBOR indexes; all `CardanoDelta` variants are appended, never reordered. Old rows decode with the new fields empty (covered by compat tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR adds persisted governance state, Conway committee handling, epoch-based DRep expiry, dormancy propagation, singleton key abstractions, and lifecycle integration across genesis, Conway transitions, rolling, boundary processing, migration, and finalization. ChangesGovernance and DRep lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GenesisOrBoundary
participant GovState
participant DeltaBuilder
participant DRepStateVisitor
participant Finalizer
GenesisOrBoundary->>GovState: seed Conway constitution and committee
DeltaBuilder->>GovState: read dormant epoch state
DeltaBuilder->>DRepStateVisitor: provide dormancy context
DRepStateVisitor->>GovState: emit dormancy reset and governance deltas
Finalizer->>GovState: apply queued singleton deltas
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
crates/cardano/src/model/dreps.rs (1)
27-83: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
setassumes non-decreasing epochs.If called with
epoch < updated_in(an out-of-order or replayed write) the rotation clobbersprevwith the newercurrentand movesupdated_inbackwards, corrupting the one-epoch look-back. Chain-ordered application makes this unreachable today, but adebug_assert!(epoch >= self.updated_in)would pin the invariant.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cardano/src/model/dreps.rs` around lines 27 - 83, Add a debug_assert! at the start of DRepExpiry::set enforcing that the incoming epoch is greater than or equal to self.updated_in, before any rotation or state mutation; preserve the existing update behavior for valid non-decreasing epochs.crates/cardano/src/model/gov.rs (2)
316-354: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
pop_authpops then removes when the entry was created.When
createdis true the history has exactly one entry, so thepop()is dead work; harmless but slightly obscures intent. Consider early-returning oncreated.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cardano/src/model/gov.rs` around lines 316 - 354, Update pop_auth to handle created entries with an early return after removing the cold key, avoiding the unnecessary history.pop() when created is true. Preserve the existing was_new handling and normal history pop behavior for entries that were not newly created.
139-150: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
committee_auth_as_ofsilently depends on ascending slot order.The reverse scan returns the first entry with
at <= slot, which is only the effective authorization if the history is sorted by slot. Appends are naturally monotonic during rolling, but nothing enforces it (andany_auth_historygenerates unsorted slots). A debug assertion or amax_by_keyfallback would make the invariant explicit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cardano/src/model/gov.rs` around lines 139 - 150, Update Committee authorization history handling in committee_auth_as_of to avoid silently relying on ascending slot order: either enforce the ordering invariant with a debug assertion at history creation/update points, or select the authorization with the greatest at value not exceeding slot via a max-by-key fallback. Account for unsorted histories produced by any_auth_history while preserving the as-of behavior.crates/cardano/src/roll/mod.rs (1)
580-589: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueKey-only scan decodes every
DRepStateneedlessly.
iter_entities_typeddecodes each value just to discard it;iter_entities(raw) would give the same keys without the CBOR work. Only hit when a dormant stretch is active, so low priority.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cardano/src/roll/mod.rs` around lines 580 - 589, Update the dormant-stretch branch in the roll logic to use the raw state iteration method instead of iter_entities_typed::<DRepState>, collecting each record’s key without decoding DRepState values. Preserve the existing error propagation and assignment to dormancy.drep_keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/cardano/src/ewrap/loading.rs`:
- Around line 275-283: Update the GovState::num_dormant_epochs transition logic
at epoch/transaction boundaries so it increments when no live proposals are
present, rather than only resetting. Preserve reset behavior when new proposals
occur, and ensure the updated counter is used by the expiry adjustment and
DRepDormancyRelease fan-out across consecutive zero-proposal epochs and
transactions.
In `@crates/cardano/src/roll/dreps.rs`:
- Around line 124-142: Update the dormancy-release handling around
DRepDormancyRelease to track keys registered by RegDRepCert certificates during
the batch crawl and union them with self.dormancy.drep_keys before fan-out.
Ensure newly registered DReps receive the same release delta using the
pre-release dormant epoch count, while preserving the existing reset behavior
and excluding invalid transactions.
---
Nitpick comments:
In `@crates/cardano/src/model/dreps.rs`:
- Around line 27-83: Add a debug_assert! at the start of DRepExpiry::set
enforcing that the incoming epoch is greater than or equal to self.updated_in,
before any rotation or state mutation; preserve the existing update behavior for
valid non-decreasing epochs.
In `@crates/cardano/src/model/gov.rs`:
- Around line 316-354: Update pop_auth to handle created entries with an early
return after removing the cold key, avoiding the unnecessary history.pop() when
created is true. Preserve the existing was_new handling and normal history pop
behavior for entries that were not newly created.
- Around line 139-150: Update Committee authorization history handling in
committee_auth_as_of to avoid silently relying on ascending slot order: either
enforce the ordering invariant with a debug assertion at history creation/update
points, or select the authorization with the greatest at value not exceeding
slot via a max-by-key fallback. Account for unsorted histories produced by
any_auth_history while preserving the as-of behavior.
In `@crates/cardano/src/roll/mod.rs`:
- Around line 580-589: Update the dormant-stretch branch in the roll logic to
use the raw state iteration method instead of iter_entities_typed::<DRepState>,
collecting each record’s key without decoding DRepState values. Preserve the
existing error propagation and assignment to dormancy.drep_keys.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ac779916-b868-4c97-93c4-6fd976ee3d00
📒 Files selected for processing (12)
crates/cardano/src/estart/commit.rscrates/cardano/src/estart/loading.rscrates/cardano/src/ewrap/loading.rscrates/cardano/src/ewrap/mod.rscrates/cardano/src/genesis/mod.rscrates/cardano/src/model/dreps.rscrates/cardano/src/model/eras.rscrates/cardano/src/model/gov.rscrates/cardano/src/model/mod.rscrates/cardano/src/pallas_extras.rscrates/cardano/src/roll/dreps.rscrates/cardano/src/roll/mod.rs
| // Epoch-based expiry, Haskell-style: the stored value carries no | ||
| // dormancy credit, so add the counter back. A DRep is active while | ||
| // `epoch <= expiry + dormant`, i.e. it expires entering the first | ||
| // epoch strictly greater. | ||
| if let Some(expiry) = &drep.expiry { | ||
| let actual = expiry.current + self.num_dormant_epochs; | ||
|
|
||
| return Ok(actual < self.starting_epoch_no()); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Any writer of num_dormant_epochs other than the reset delta
rg -nP -C4 'num_dormant_epochs' crates/cardano/srcRepository: txpipe/dolos
Length of output: 8729
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Files referencing GovDormancyReset / num_dormant_epochs in model/gov.rs =="
rg -n -C 6 'GovDormancyReset|num_dormant_epochs|DRepDormancyRelease' crates/cardano/src/model/gov.rs
echo
echo "== Write/delete/usages in cardano crate =="
rg -n 'num_dormant_epochs|GovState' crates/cardano/src | sed -n '1,220p'
echo
echo "== Candidate model functions around GovDormancyReset =="
sed -n '360,490p' crates/cardano/src/model/gov.rsRepository: txpipe/dolos
Length of output: 12842
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== All references to GovDormancyReset =="
rg -n -C 4 'GovDormancyReset|DRepDormancyRelease' crates/cardano/src || true
echo
echo "== Cardano model delta variants involving gov/dreps =="
sed -n '60,185p' crates/cardano/src/model/mod.rsRepository: txpipe/dolos
Length of output: 22229
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== roll modulo declarations for dreps/governance =="
sed -n '1,120p' crates/cardano/src/roll/mod.rs
echo
sed -n '120,230p' crates/cardato/src/roll/mod.rs 2>/dev/null || sed -n '120,230p' crates/cardano/src/roll/mod.rs
echo
echo "== All references to DormancyContext / dormant_epochs =="
rg -n -C 4 'DormancyContext|dormant_epochs' crates/cardano/srcRepository: txpipe/dolos
Length of output: 24989
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== DRepStateVisitor proposal/cert handling =="
sed -n '110,175p' crates/cardano/src/roll/dreps.rs
echo
echo "== Any non-test mutation of num_dormant_epochs/dormant_epochs in .rs =="
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path('crates/cardano/src').rglob('*.rs')):
text = path.read_text()
if re.search(r'\bnum_dormant_epochs\s*=\s*|^\.num_dormant_epochs\s*=', text, re.M) or re.search(r'\bdormant_epochs\s*=\s*|^\.dormant_epochs\s*=', text, re.M):
lines = text.splitlines()
for i,line in enumerate(lines,1):
if re.search(r'num_dormant_epochs\s*[+\-*/&^<>=]=|dormant_epochs\s*[+\-*/&^<>=]=', line) or (re.search(r'^\s*(num_dormant_epochs|dormant_epochs)\s*=', line) and 'let mut' in text[:len(''.join(lines[:i]))].count('let mut') % 2 == 0):
print(f'{path}:{i}:{line}')
PYRepository: txpipe/dolos
Length of output: 2581
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== All write sites for the relevant dormant counters =="
rg -n -C 3 'num_dormant_epochs\s*[+\-*/&^<>=]?=\s*|dormant_epochs\s*[+\-*/&^<>=]?=\s*' crates/cardano/src
echo
echo "== DeltaBuilder construction / iteration around BlockVisitor calls =="
sed -n '230,320p' crates/cardano/src/roll/mod.rsRepository: txpipe/dolos
Length of output: 5264
Increment the dormant-epoch counter on epoch/transaction boundaries with no new proposals.
Every writer of GovState::num_dormant_epochs only resets it, so epochs without live proposals keep the boundary value at 0. That makes this expiry adjustment and the DRepDormancyRelease fan-out only work if the previous epoch ended with a non-zero counter; the missing increment should preserve/drop the per-epoch activity state and account for multi-tx zero-proposal epochs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/cardano/src/ewrap/loading.rs` around lines 275 - 283, Update the
GovState::num_dormant_epochs transition logic at epoch/transaction boundaries so
it increments when no live proposals are present, rather than only resetting.
Preserve reset behavior when new proposals occur, and ensure the updated counter
is used by the expiry adjustment and DRepDormancyRelease fan-out across
consecutive zero-proposal epochs and transactions.
Review follow-ups on the phase-3 governance PR: - The dormancy-release fan-out targeted only the DRep key set snapshotted at batch start, missing DReps registered in an earlier block or tx of the same batch. Haskell's updateDormantDRepExpiry folds over the live DRep map, which includes them. The visitor now records registrations seen while the counter is non-zero and the release targets the deduplicated union (a re-registration would otherwise be boosted twice); the whole DormancyContext is threaded across the batch's builders instead of just the counter. - Collect fan-out keys via the raw entity iterator, skipping the per-row CBOR decode. - Document the DRepRegistration-precedes-DRepExpiryUpdate ordering invariant at the defensive creation site in DRepExpiryUpdate::apply. - Cover the review's test gaps: entering_conway boundary math, bootstrap_gov gating on force_protocol (via ToyDomain), the entity-absent targeted gov commit path in estart, and the release-target union/dedup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gov singleton landed with ad-hoc plumbing around machinery shaped for entity populations: a bespoke apply_gov_state_deltas in the estart commit (streams can't create absent rows), inline (ns, key) derivation at every read site, and duplicated genesis seeding between bootstrap_gov and GovGenesisInit. EpochState had grown its own parallel workarounds: an EpochState-specific apply variant in the ewrap commit and full-namespace streams to touch one known row. Name the pattern instead: a SingletonEntity trait (fixed key over FixedNamespace) implemented by GovState and EpochState, a generic creation-capable WorkDeltas::apply_singleton used by all boundary commit paths, and a read_singleton helper for direct reads. The two gov birth paths now share GovState::seed_genesis so they can't drift. No behavior change; the estart gov tests now exercise the generic path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/cardano/src/roll/dreps.rs (1)
151-170: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winReject phase-2-invalid transactions before DRep processing.
visit_txskips dormancy release for invalid transactions but still emitsDRepActivityfor their votes.visit_certsimilarly gates committee effects only, then can emit DRep registration, anchor, unregistration, and activity effects. These effects must not alter persisted governance state.
crates/cardano/src/roll/dreps.rs#L151-L170: return early for!tx.is_valid()before processing voting procedures.crates/cardano/src/roll/dreps.rs#L211-L225: return early for!tx.is_valid()before processing DRep certificates.Proposed fix
let MultiEraTx::Conway(conway_tx) = tx else { return Ok(()); }; +if !tx.is_valid() { + return Ok(()); +} -if tx.is_valid() && self.dormancy.dormant_epochs > 0 && !tx.gov_proposals().is_empty() { +if self.dormancy.dormant_epochs > 0 && !tx.gov_proposals().is_empty() {-// Committee certificates target the governance singleton. -if tx.is_valid() { - // ... +if !tx.is_valid() { + return Ok(()); } +// Committee and DRep certificate effects are valid-only.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/cardano/src/roll/dreps.rs` around lines 151 - 170, Reject phase-2-invalid transactions at the start of visit_tx’s voting-procedure processing, before dormancy release and any DRep activity effects; update crates/cardano/src/roll/dreps.rs:151-170 accordingly. Add the same !tx.is_valid() early return at the start of visit_cert’s DRep-certificate processing in crates/cardano/src/roll/dreps.rs:211-225, before registration, anchor, unregistration, or activity effects are emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@crates/cardano/src/roll/dreps.rs`:
- Around line 151-170: Reject phase-2-invalid transactions at the start of
visit_tx’s voting-procedure processing, before dormancy release and any DRep
activity effects; update crates/cardano/src/roll/dreps.rs:151-170 accordingly.
Add the same !tx.is_valid() early return at the start of visit_cert’s
DRep-certificate processing in crates/cardano/src/roll/dreps.rs:211-225, before
registration, anchor, unregistration, or activity effects are emitted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6d20beec-655f-4693-9a2e-77c720ecafcb
📒 Files selected for processing (14)
crates/cardano/src/estart/commit.rscrates/cardano/src/ewrap/commit.rscrates/cardano/src/ewrap/loading.rscrates/cardano/src/genesis/mod.rscrates/cardano/src/lib.rscrates/cardano/src/model/dreps.rscrates/cardano/src/model/epochs.rscrates/cardano/src/model/eras.rscrates/cardano/src/model/gov.rscrates/cardano/src/model/mod.rscrates/cardano/src/roll/batch.rscrates/cardano/src/roll/dreps.rscrates/cardano/src/roll/mod.rscrates/cardano/src/rupd/work_unit.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- crates/cardano/src/model/eras.rs
- crates/cardano/src/ewrap/loading.rs
- crates/cardano/src/model/gov.rs
- crates/cardano/src/model/dreps.rs
The gov singleton had conditional, era-dependent existence: born at genesis only for forced-Conway devnets, at the Chang boundary otherwise, and never on in-place-upgraded stores. Every delta and read paid for that with absence tolerance — was_new undo flags, get_or_insert apply paths, unwrap_or_default reads — a tax each future gov delta would inherit. Flip the model to an invariant plus a migration: - The row now exists on every store regardless of era. Bootstrap always creates it; a new active_since field (None = pre-Conway) flags whether governance is enabled, set to 0 for forced-Conway networks and to the boundary epoch by GovGenesisInit at Chang. - A one-time startup migration (CARDANO-006) creates the row on stores bootstrapped before the invariant, deriving active_since from the era summary via the new ChainSummary::first_conway_epoch. Enact-state fields stay unset — content missed since the fork only comes from a fresh sync. Runs in CardanoLogic::initialize, before WAL catch-up replays deltas that now expect the row. - Deltas assert existence: GovGenesisInit activates (and undoes to) the inactive row instead of creating/removing it; CommitteeAuth, CommitteeResign and GovDormancyReset drop their was_new machinery. - Reads assert too: load_gov replaces the tolerant unwrap_or_default reads, erroring with CARDANO-007 (MissingGovState) on a broken store. Note for dev environments: stores synced with earlier commits of this branch must be wiped — the WAL shapes of the gov deltas changed and existing gov rows carry no active_since. No released store is affected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plan
Phase 3 of the Trellis plan
plans/dolos-governance-gaps.md(Brain/txpipe, commit35819c5): Committee, constitution, gov singleton — newGovStateentity; handle the two committee certificates; epoch-based DRep expiry + dormancy bookkeeping onDRepState. Technical spec:solution/dolos/kb/cardano-ledger-governance-gaps/dolos-implementation-design.md(§3.2, §3.3, §4 roll-time deltas, §5.1); semantics source:haskell-governance-research.md(§2.6, §3.1, §3.3), cross-checked againstIntersectMBO/cardano-ledger(Conway/Rules/GovCert.hs,Conway/Rules/Certs.hs).Done criterion (phase 3) — met: the
GovStatesingleton exists and is seeded, both committee certificates mutate it, andDRepStatecarries Haskell-style epoch-based expiry with dormancy-release bookkeeping. Ratification-side consumers of this state (tallies, dormancy tick, committee GC, roots updates) are phases 4–5 by design.What changed
New
GovStateentity (ns"gov", keyb"0") —model/gov.rsconstitution,committee(cold cred → term epoch, threshold),committee_auths(slot-stamped append-only histories, as-of readable),prev_gov_action_ids(four per-purpose roots, written by phase 5),num_dormant_epochs.GovGenesisInit, emitted in ESTART finalize with a targeted commit path that tolerates the entity not existing) and at bootstrap forforce_protocol >= 9devnets. Stores upgraded in place past Chang get no seed — stale data would be worse than the documented "history absent" gap (design §6).Committee certificates —
roll/dreps.rs,pallas_extras.rsAuthCommitteeHot→CommitteeAuth;ResignCommitteeCold→CommitteeResign; both append to the cold credential's history. Valid (phase-2) txs only, matching the phase-2 precedent for new visitors.Epoch-based DRep expiry —
model/dreps.rs,roll/dreps.rs,ewrap/loading.rsDRepState.expiry: Option<DRepExpiry>at CBOR index 8:(current, updated_in, prev)— stored without dormancy credit exactly like Haskell'sdrepExpiry; the epoch-rotated pair gives the as-of-previous-boundary read phase 5's tally needs.DRepExpiryUpdateemitted onRegDRepCert(PV9 bootstrap ignores dormancy credit, percomputeDRepExpiryVersioned),UpdateDRepCert, and every DRep vote (only_if_registered, mirroring the HaskellMap.adjust). A refresh clears theexpiredflag (expiry is implicit in the ledger).should_expire_drepnow usesstored + num_dormant_epochs < starting_epoch(exact ledger semantics; the old slot heuristic remains as fallback for pre-upgrade rows, which self-heal).Dormancy release —
roll/dreps.rs,roll/mod.rsDRepDormancyReleasefan-out (skips long-expired and legacy rows) +GovDormancyReset. Runs before certs/votes of the same tx — verified against the Haskell CERTS rule, whose terminal case runs first. The counter is threaded through the batch so later blocks see the reset. (The counter increment is the EPOCH-rule dormancy tick — phase 5 per the plan; until then the counter stays 0 and the release machinery is dormant but exact.)Frozen-format discipline (design §6)
CardanoDeltavariants appended afterVoteCast; no existing variant or field layout touched.Option/defaulted additions at unused CBOR indexes (DRepStateindex 8;"gov"is a fresh namespace, no migration burden). Compat tests encode pre-phase-3 row replicas and decode them with the new fields empty.initialize_schema; fjall uses a unified keyspace with hashed namespace prefixes (no per-namespace setup).Coder interpretations to sample
(value, updated_in, prev)record for DRep expiry (avoids couplingDRepStateto the ESTARTEpochValuerotation pass) and slot-stamped histories for committee auths (named directly by design §3.3). Both alternatives deliver identical observable behavior; this is the implementation reading, recorded here for review.Committee.membersis aBTreeMap(Haskell:Map ColdCred EpochNo) rather than the design sketch'sVec<(cold, epoch)>— dedup + lookup semantics match the ledger; encodes fine.DRepUpdate { anchor, expiry_refresh }is realized as the existing phase-1DRepAnchorUpdateplus the newDRepExpiryUpdate; the design'sDormancyFoldis the sanctioned per-DRep fan-out (DRepDormancyRelease+GovDormancyReset).Verification
cargo +nightly fmt --all -- --check— clean.cargo clippy --all-targets --all-features -- -D warnings— clean.cargo test --workspace --all-targets— all suites pass locally (new: entity CBOR round-trips, legacy-row decode compat forDRepStateandProposalState, delta apply/undo + WAL-serde proptests for all six new deltas,DRepExpiryrotation/as-of unit tests, dormancy fold semantics incl. no-resurrect, mainnet Conway genesis mapping).--ignorednetwork/OCI suites (smoke, sync, snapshot, epoch fixtures) — CI covers these; the epoch fixtures are the only end-to-end exercise of the Chang-boundary seeding path.Escalations
None.
Breaking note for dev stores
The gov singleton's existence is now an invariant (created at bootstrap for every store,
active_sinceflags pre-Conway vs active, and a one-time CARDANO-006 startup migration covers stores bootstrapped before the invariant). Stores synced with earlier commits of this branch must be wiped: the WAL shapes ofGovGenesisInit/CommitteeAuth/CommitteeResign/GovDormancyResetchanged, and pre-existing gov rows carry noactive_since. No released store is affected — these shapes never shipped. In-place-upgraded stores past Chang get the row from the migration with enact-state unset; complete governance content still requires a fresh sync.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes