Skip to content
Open
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
22929d1
build(governance-rewards): vendor reward-assignment 1.0.0 + amulet 0.…
gyorgybalazsi Jul 15, 2026
350e38d
feat(governance-rewards): AssignRewardBeneficiaries governable action
gyorgybalazsi Jul 15, 2026
ed17e13
feat(decman): ProposalType::AssignRewardBeneficiaries + boundary vali…
gyorgybalazsi Jul 15, 2026
acfba71
feat(decman): serialize AssignRewardBeneficiaries proposal args
gyorgybalazsi Jul 15, 2026
241f638
fix(decman): AssignRewardBeneficiaries coupon fields are contract-id …
gyorgybalazsi Jul 15, 2026
87b4b3b
docs(reward-cranker): add Mode A design spec + M1/M2 plan for review
gyorgybalazsi Jul 15, 2026
7684a17
docs(reward-cranker): correct assign wording (share of amount, not pe…
gyorgybalazsi Jul 15, 2026
f83e75d
docs(coupon-reassignment): rename from "reward cranker"
gyorgybalazsi Jul 15, 2026
fc9b6ae
docs(coupon-reassignment): apply "reward cranker" → "automation" sweep
gyorgybalazsi Jul 15, 2026
b7aaf8a
docs(coupon-reassignment): land verified M3+M4 plan; sync design spec
gyorgybalazsi Jul 20, 2026
d48523b
chore(governance-rewards): reconcile M1/M2 with #256 (0.1.2)
gyorgybalazsi Jul 20, 2026
c35187b
docs(coupon-reassignment): sync M3+M4 plan (post-rebase update)
gyorgybalazsi Jul 20, 2026
4b5a1e7
feat(governance-rewards): RewardSplitConfig + SetRewardSplit action
gyorgybalazsi Jul 20, 2026
35b2121
feat(decman): ProposalType::SetRewardSplit + serializer
gyorgybalazsi Jul 20, 2026
2693775
refactor(decman): extract pub(crate) submit_proposal + resolve_active…
gyorgybalazsi Jul 20, 2026
87c0bfb
feat(decman): reward-automation SplitSource + unassigned-coupon reader
gyorgybalazsi Jul 20, 2026
5917bba
refactor(decman): SplitSource as a plain async fn (drop async-trait)
gyorgybalazsi Jul 20, 2026
b447f7a
feat(decman): auto-confirm engine + assign-proposal parse-back
gyorgybalazsi Jul 20, 2026
f766640
feat(decman): proposer + confirmer roles
gyorgybalazsi Jul 20, 2026
db0b402
feat(decman): register per-node reward-automation loop
gyorgybalazsi Jul 20, 2026
0701348
fix(decman): proposer self-confirm (C-1 liveness) + review hardening
gyorgybalazsi Jul 20, 2026
704c3d3
perf(decman): fetch pending assign proposals once per tick (M-5)
gyorgybalazsi Jul 20, 2026
a49767e
docs(coupon-reassignment): sync plan (M-5 done, M-4 deferred)
gyorgybalazsi Jul 20, 2026
2f3f701
docs(coupon-reassignment): sync plan (review findings renamed M-N -> …
gyorgybalazsi Jul 20, 2026
3ecfd7e
docs(coupon-reassignment): sync reconciled M3+M4 plan (matches shippe…
gyorgybalazsi Jul 20, 2026
b1509bc
docs(coupon-reassignment): sync reconciled design spec (matches decis…
gyorgybalazsi Jul 20, 2026
20621d1
docs(coupon-reassignment): sync design (Mode B future-policy light to…
gyorgybalazsi Jul 20, 2026
4a42134
test(integration): feature-gated Mode A reward-assignment e2e (Task 1…
gyorgybalazsi Jul 20, 2026
d421915
feat(decman): env-configurable reward-automation interval
gyorgybalazsi Jul 20, 2026
a9e7bf7
docs: remove personal name from reward-automation code comments
gyorgybalazsi Jul 21, 2026
93a2840
docs(coupon-reassignment): sync delegation design + migration plan fr…
gyorgybalazsi Jul 21, 2026
0373cd0
docs(coupon-reassignment): banner + de-name the superseded m1-m2/m3-m…
gyorgybalazsi Jul 21, 2026
5503e5e
docs(coupon-reassignment): drop deprecated 'Daml SDK' framing (dpm is…
gyorgybalazsi Jul 21, 2026
982e765
docs(coupon-reassignment): rename delegation field executors -> assig…
gyorgybalazsi Jul 21, 2026
0c4edb9
docs(coupon-reassignment): DAML tests use TestHarness GWT style (sync…
gyorgybalazsi Jul 21, 2026
0dfe074
docs(coupon-reassignment): Task 1 relocates mkUnassignedCoupon to Ass…
gyorgybalazsi Jul 21, 2026
dba3223
feat(governance-rewards): CouponReassignmentDelegation + Delegation_A…
gyorgybalazsi Jul 21, 2026
4533865
fix(governance-rewards): repoint all gov-rewards deps to 0.1.3 (clean…
gyorgybalazsi Jul 21, 2026
c804668
docs(coupon-reassignment): version bump repoints all gov-rewards pins…
gyorgybalazsi Jul 21, 2026
a6ad78a
feat(governance-rewards): Setup/Revoke CouponReassignmentDelegation g…
gyorgybalazsi Jul 21, 2026
0123b46
feat(decman): ProposalType Setup/Revoke CouponReassignmentDelegation …
gyorgybalazsi Jul 21, 2026
05d16f2
feat(decman): active CouponReassignmentDelegation reader (enablement …
gyorgybalazsi Jul 21, 2026
fbf5280
feat(decman): per-round Delegation_Assign assigner (plain 1-of-n exer…
gyorgybalazsi Jul 21, 2026
4dc6cd8
refactor(decman): rewrite reward-automation loop to the delegation model
gyorgybalazsi Jul 21, 2026
c95957a
refactor(decman): delete auto-confirm engine (proposer/confirmer/spli…
gyorgybalazsi Jul 21, 2026
e80277f
refactor: remove AssignRewardBeneficiaries/RewardSplitConfig/SetRewar…
gyorgybalazsi Jul 21, 2026
05148d6
docs(coupon-reassignment): Task 9 creates the IT phase (sync)
gyorgybalazsi Jul 21, 2026
3cad846
test(integration): delegation-model coupon reassignment on devnet
gyorgybalazsi Jul 21, 2026
8ad99da
fix(decman): correct Delegation_Assign template module path + lock wi…
gyorgybalazsi Jul 21, 2026
1cf98fe
docs(coupon-reassignment): fix Task 5 template_id module_name (sync)
gyorgybalazsi Jul 21, 2026
f6a716f
Merge origin/main into coupon-reassignment automation branch
gyorgybalazsi Jul 23, 2026
d569cf1
test(it): distribute governance-rewards DAR to localnet participants
gyorgybalazsi Jul 24, 2026
904d569
test(it): JSON Ledger API helpers for coupon seeding + split assertion
gyorgybalazsi Jul 24, 2026
44e2bf3
test(it): reuse post_expect_status in active_reward_coupons; tighten …
gyorgybalazsi Jul 24, 2026
2173ceb
test(it): add reward_beneficiary_party/reward_operator_party to Fixture
gyorgybalazsi Jul 24, 2026
95d6454
test(it): seed unassigned RewardCouponV2 on localnet via JSON Ledger API
gyorgybalazsi Jul 24, 2026
6dd7478
test(it): localnet disjoint beneficiaries + 0.8/0.2 split-by-value as…
gyorgybalazsi Jul 24, 2026
5eb6ef0
test(it): fix stale precondition comment (localnet hard-fails, devnet…
gyorgybalazsi Jul 24, 2026
0c76f7f
test(it): run coupon-reassignment on every localnet CI run; 15s autom…
gyorgybalazsi Jul 24, 2026
dc4fc38
test(it): surface split-assertion read errors instead of masking as t…
gyorgybalazsi Jul 24, 2026
eecd6fe
test(it): query concrete RewardCouponV2 on localnet reward IT
gyorgybalazsi Jul 24, 2026
c3a3ef2
test(it): propose_confirm_execute targets its own proposal, not the s…
gyorgybalazsi Jul 24, 2026
2efebc8
fix(decman): preserve propose_action HTTP status codes
gyorgybalazsi Jul 24, 2026
507b94f
docs(coupon-reassignment): correct emptyExtraArgs origin
gyorgybalazsi Jul 24, 2026
286f1ac
fix(decman): unconfigured governance package returns 503, add status …
gyorgybalazsi Jul 24, 2026
44476bf
fix(decman): reject duplicate beneficiaries in reward split validation
gyorgybalazsi Jul 24, 2026
7c8d100
feat(decman): warn when ripe coupons exceed one reassign batch
gyorgybalazsi Jul 24, 2026
c367921
test(decman): assert serializer value shapes for reassignment proposals
gyorgybalazsi Jul 24, 2026
decc32f
test(governance-rewards): cover setup validation reject branches
gyorgybalazsi Jul 24, 2026
92680a3
fix(decman): clamp reward-automation interval and unit-test coupon fi…
gyorgybalazsi Jul 24, 2026
a37c86f
fix(decman): reject empty delegation cid in revoke validation
gyorgybalazsi Jul 24, 2026
6f0eb9f
docs(governance-rewards): document the coupon-reassignment (Mode A) path
gyorgybalazsi Jul 24, 2026
671255b
style(decman): apply rustfmt to review-fix changes
gyorgybalazsi Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/decman/frontend/src/components/GovernanceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,14 @@ export const GovernanceSection = ({
throw new Error(
"Paid credential proposal forms are not implemented yet — use the Free direction or call the API directly.",
);
default:
// Automation-only proposal types (e.g. setup_coupon_reassignment_delegation,
// revoke_coupon_reassignment_delegation) have no UI form by design and are
// never offered in the menu; guard the exhaustiveness so `proposal` is
// always assigned.
throw new Error(
`Proposal type "${proposalType}" is automation-only and not available in the UI.`,
);
}

const request: ProposeActionRequest = {
Expand Down
6 changes: 6 additions & 0 deletions crates/decman/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,5 +195,11 @@ pub enum Commands {
/// Backoff between attempts of the bounded peer-Noise retry wrapper, in milliseconds
#[arg(long, env = "DECPM_NOISE_RETRY_BACKOFF_MS")]
noise_retry_backoff_ms: Option<u64>,

/// CIP-104 Mode A reward-automation loop tick interval, in seconds.
/// Enablement is on-ledger (presence of a CouponReassignmentDelegation);
/// this only controls cadence. Defaults to 300.
#[arg(long, env = "DECPM_REWARD_AUTOMATION_INTERVAL_SECS")]
reward_automation_interval_secs: Option<u64>,
},
}
5 changes: 5 additions & 0 deletions crates/decman/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,10 @@ pub struct NodeConfig {
pub canton: CantonConfig,
pub timeouts: Timeouts,
pub noise_retry: NoiseRetryConfig,
/// Tick interval (seconds) for the CIP-104 Mode A reward-assignment
/// automation loop. Enablement is on-ledger (presence of a
/// `CouponReassignmentDelegation`), so this only controls cadence. Default 300s.
pub reward_automation_interval_secs: u64,
/// Top-level Keycloak config for frontend website gating
pub keycloak: Option<KeycloakConfig>,
/// Top-level Auth0 config for frontend website gating (mutually exclusive
Expand All @@ -272,6 +276,7 @@ impl Default for NodeConfig {
canton: CantonConfig::default(),
timeouts: Timeouts::default(),
noise_retry: NoiseRetryConfig::default(),
reward_automation_interval_secs: 300,
keycloak: None,
auth0: None,
insecure: false,
Expand Down
4 changes: 4 additions & 0 deletions crates/decman/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ async fn main() -> Result {
noise_retry_timeout_sec,
noise_retry_max_attempts,
noise_retry_backoff_ms,
reward_automation_interval_secs,
db_encryption_key,
insecure,
canton_hmac_secret,
Expand Down Expand Up @@ -181,6 +182,9 @@ async fn main() -> Result {
if let Some(v) = noise_retry_backoff_ms {
config.noise_retry.backoff_ms = *v;
}
if let Some(v) = reward_automation_interval_secs {
config.reward_automation_interval_secs = *v;
}

config.insecure = *insecure;
// Only ingest the unsafe HMAC settings when insecure mode is on;
Expand Down
133 changes: 128 additions & 5 deletions crates/decman/src/server/action_serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ use crate::{canton_id::CantonId, error::Result};

use super::types::{
ActionType, AppRewardBeneficiary, BillingParams, Claim, FarConfig, InstrumentAllowance,
InstrumentId, InstrumentIdentifier, ProposalType, VaultLimits,
InstrumentId, InstrumentIdentifier, ProposalType, RewardBeneficiary, VaultLimits,
};

// ============================================================================
// Helper Functions
// ============================================================================

fn make_party(p: impl std::fmt::Display) -> Value {
pub(crate) fn make_party(p: impl std::fmt::Display) -> Value {
Value {
sum: Some(value::Sum::Party(p.to_string())),
}
Expand Down Expand Up @@ -53,13 +53,13 @@ fn make_bool(b: bool) -> Value {
}
}

fn make_contract_id(c: &str) -> Value {
pub(crate) fn make_contract_id(c: &str) -> Value {
Value {
sum: Some(value::Sum::ContractId(c.to_string())),
}
}

fn field(label: &str, value: Value) -> RecordField {
pub(crate) fn field(label: &str, value: Value) -> RecordField {
RecordField {
label: label.to_string(),
value: Some(value),
Expand All @@ -85,7 +85,7 @@ fn make_variant(constructor: &str, value: Value) -> Value {
}
}

fn make_list(values: Vec<Value>) -> Value {
pub(crate) fn make_list(values: Vec<Value>) -> Value {
Value {
sum: Some(value::Sum::List(List { elements: values })),
}
Expand Down Expand Up @@ -249,6 +249,14 @@ fn make_optional_numeric(opt: &Option<DamlDecimal>) -> Value {
}
}

fn make_optional_contract_id(opt: &Option<String>) -> Value {
Value {
sum: Some(value::Sum::Optional(Box::new(Optional {
value: opt.as_ref().map(|c| Box::new(make_contract_id(c))),
}))),
}
}

fn make_optional_bool(opt: &Option<bool>) -> Value {
Value {
sum: Some(value::Sum::Optional(Box::new(Optional {
Expand Down Expand Up @@ -328,6 +336,13 @@ fn serialize_app_reward_beneficiary(b: &AppRewardBeneficiary) -> Value {
])
}

fn serialize_reward_beneficiary(b: &RewardBeneficiary) -> Value {
make_record(vec![
field("beneficiary", make_party(&b.beneficiary)),
field("percentage", make_numeric(&b.percentage.to_string())),
])
}

fn serialize_instrument_identifier(i: &InstrumentIdentifier) -> Value {
make_record(vec![
field("source", make_party(&i.source)),
Expand Down Expand Up @@ -1184,6 +1199,52 @@ pub fn build_proposal_create_args(
],
},
),
ProposalType::SetupCouponReassignmentDelegation {
assigners,
new_beneficiaries,
prior_delegation,
} => (
ProposalPackage::GovernanceRewards,
"Governance.Rewards.SetupCouponReassignmentDelegation",
"SetupCouponReassignmentDelegation",
Record {
record_id: None,
fields: vec![
field("governanceParty", make_party(governance_party)),
field("proposer", make_party(proposer)),
field(
"priorDelegation",
make_optional_contract_id(prior_delegation),
),
field(
"assigners",
make_list(assigners.iter().map(make_party).collect()),
),
field(
"beneficiaries",
make_list(
new_beneficiaries
.iter()
.map(serialize_reward_beneficiary)
.collect(),
),
),
],
},
),
ProposalType::RevokeCouponReassignmentDelegation { delegation } => (
ProposalPackage::GovernanceRewards,
"Governance.Rewards.RevokeCouponReassignmentDelegation",
"RevokeCouponReassignmentDelegation",
Record {
record_id: None,
fields: vec![
field("governanceParty", make_party(governance_party)),
field("proposer", make_party(proposer)),
field("delegation", make_contract_id(delegation)),
],
},
),
ProposalType::SetEnableResultContracts {
registrar_service_cid,
enable_result_contracts,
Expand Down Expand Up @@ -2075,6 +2136,11 @@ mod tests {
CantonId::new("p".to_string(), Namespace::new([0u8; NAMESPACE_LENGTH]))
}

/// Parse a decimal literal in test fixtures, panicking on invalid input.
fn dec(s: &str) -> DamlDecimal {
DamlDecimal::parse(s).expect("valid decimal literal")
}

/// Unwrap a `Variant` value into `(constructor, inner)`.
fn as_variant(value: &Value) -> (&str, &Value) {
match &value.sum {
Expand Down Expand Up @@ -2466,6 +2532,63 @@ mod tests {
Ok(())
}

#[test]
fn build_proposal_setup_delegation_shape() -> Result {
let proposal = ProposalType::SetupCouponReassignmentDelegation {
assigners: vec![party_id(), party_id()],
new_beneficiaries: vec![
RewardBeneficiary {
beneficiary: party_id(),
percentage: dec("0.8"),
},
RewardBeneficiary {
beneficiary: party_id(),
percentage: dec("0.2"),
},
],
prior_delegation: Some("00old".to_string()),
};
let (package, module, entity, record) =
build_proposal_create_args("gov", "proposer", &proposal, None, None)?;
assert_eq!(package, ProposalPackage::GovernanceRewards);
assert_eq!(
module,
"Governance.Rewards.SetupCouponReassignmentDelegation"
);
assert_eq!(entity, "SetupCouponReassignmentDelegation");
assert_eq!(
owned_labels(&record),
[
"governanceParty",
"proposer",
"priorDelegation",
"assigners",
"beneficiaries"
]
);
Ok(())
}

#[test]
fn build_proposal_revoke_delegation_shape() -> Result {
let proposal = ProposalType::RevokeCouponReassignmentDelegation {
delegation: "00abc".to_string(),
};
let (package, module, entity, record) =
build_proposal_create_args("gov", "proposer", &proposal, None, None)?;
assert_eq!(package, ProposalPackage::GovernanceRewards);
assert_eq!(
module,
"Governance.Rewards.RevokeCouponReassignmentDelegation"
);
assert_eq!(entity, "RevokeCouponReassignmentDelegation");
assert_eq!(
owned_labels(&record),
["governanceParty", "proposer", "delegation"]
);
Ok(())
}

#[test]
fn build_proposal_accept_transfer_shape_and_context_branches() -> Result {
let proposal = ProposalType::AcceptTransfer {
Expand Down
Loading