@@ -285,9 +285,16 @@ pub use crate::config_metadata::LAST_CFG_UPDATE_KEY;
285285//
286286// ===== Event Payload Schemas =====
287287//
288- // sla_calc → (outage_id: Symbol, status: Symbol, payment_type: Symbol,
289- // rating: Symbol, mttr_minutes: u32, threshold_minutes: u32,
290- // amount: i128, config_version_hash: u64, recorded_at: u64)
288+ // The three decision-carrying events (sla_calc, set_int, dup_input) share a
289+ // single canonical field order — the SLAResult struct order — so indexers
290+ // parse one layout regardless of which decision event they consume (#429):
291+ //
292+ // decision → (outage_id, status, mttr_minutes, threshold_minutes, amount,
293+ // payment_type, rating, config_version_hash, recorded_at)
294+ //
295+ // sla_calc → (outage_id: Symbol, status: Symbol, mttr_minutes: u32,
296+ // threshold_minutes: u32, amount: i128, payment_type: Symbol,
297+ // rating: Symbol, config_version_hash: u64, recorded_at: u64)
291298// context: severity Symbol
292299//
293300// cfg_upd → (threshold_minutes: u32, penalty_per_minute: i128,
@@ -333,11 +340,9 @@ pub use crate::config_metadata::LAST_CFG_UPDATE_KEY;
333340// op_can → ()
334341// context: caller Address
335342//
336- // op_sup → (superseded_operator: Address, new_operator: Address)
337- // context: caller Address
338- //
339- // set_int → (outage_id: Symbol, status: Symbol, payment_type: Symbol,
340- // amount: i128, config_version_hash: u64, recorded_at: u64)
343+ // set_int → (outage_id: Symbol, status: Symbol, mttr_minutes: u32,
344+ // threshold_minutes: u32, amount: i128, payment_type: Symbol,
345+ // rating: Symbol, config_version_hash: u64, recorded_at: u64)
341346// context: severity Symbol
342347//
343348// dup_input → (outage_id: Symbol, status: Symbol, mttr_minutes: u32,
@@ -359,8 +364,13 @@ pub(crate) const EVENT_SLA_CALC: Symbol = symbol_short!("sla_calc");
359364
360365/// Emitted alongside sla_calc for settlement intent reconciliation.
361366///
362- /// Compatibility decision: settlement intent fields are ordered by settlement
363- /// priority (id, status, payment, amount, hash, timestamp). Field additions
367+ /// Carries the full SLA decision (including `mttr_minutes`,
368+ /// `threshold_minutes`, and `rating`) so a consumer processing only the
369+ /// settlement stream can reconstruct the decision without a follow-up read.
370+ ///
371+ /// Compatibility decision: shares the canonical decision field order
372+ /// (`outage_id, status, mttr_minutes, threshold_minutes, amount,
373+ /// payment_type, rating, config_version_hash, recorded_at`). Field additions
364374/// go at the end; any reorder or removal requires a version bump.
365375pub ( crate ) const EVENT_SETTLE_INTENT : Symbol = symbol_short ! ( "set_int" ) ;
366376
@@ -854,8 +864,9 @@ pub struct PublicApiMethod {
854864 /// Auth classification. Values:
855865 /// - `"admin"` – caller must hold the admin role.
856866 /// - `"operator"` – caller must hold the operator role.
857- /// - `"multi"` – two or more parties must authorize (e.g. `initialize`
858- /// requires BOTH admin and operator signatures) (#425).
867+ /// - `"addr"` – only a specific stored address may call (the pending
868+ /// proposal slot holder must sign, e.g. `accept_admin`/`accept_operator`)
869+ /// (#426).
859870 /// - `"none"` – no authorization gate (read-only / public).
860871 pub auth : Symbol ,
861872 /// The primary event name emitted by this method, or `Symbol::new(env, "")` if none.
@@ -2170,8 +2181,9 @@ impl SLACalculatorContract {
21702181 /// Each `PublicApiMethod` contains:
21712182 /// - `name`: the contract method name (e.g. "calculate_sla")
21722183 /// - `mutates`: `true` if the method modifies storage
2173- /// - `auth`: auth classification — `"admin"`, `"operator"`, `"multi"`
2174- /// (multiple parties, e.g. `initialize`), or `"none"`.
2184+ /// - `auth`: auth classification — `"admin"`, `"operator"`, `"addr"`
2185+ /// (a specific pending address, e.g. `accept_admin`/`accept_operator`),
2186+ /// or `"none"`.
21752187 /// - `event`: the primary event name emitted, or empty if none
21762188 ///
21772189 /// # Errors
@@ -2210,10 +2222,12 @@ impl SLACalculatorContract {
22102222 // All public methods added in alphabetical order for deterministic output.
22112223 // Lifecycle:
22122224 // Note: accept_admin/accept_operator are called by the proposed address
2213- // (not the current role holder), so auth is "none" — only an address
2214- // equality check against the pending slot is performed.
2215- methods. push_back ( method ( "accept_admin" , true , "none" , "adm_acc" ) ) ;
2216- methods. push_back ( method ( "accept_operator" , true , "none" , "op_acc" ) ) ;
2225+ // (not the current role holder). They still call `caller.require_auth()`
2226+ // and enforce that the caller equals the pending address, so they are
2227+ // NOT "none" — they are address-scoped ("addr"— the pending slot holder
2228+ // must sign) (#426).
2229+ methods. push_back ( method ( "accept_admin" , true , "addr" , "adm_acc" ) ) ;
2230+ methods. push_back ( method ( "accept_operator" , true , "addr" , "op_acc" ) ) ;
22172231 // Calculation:
22182232 methods. push_back ( method ( "calculate_sla" , true , "operator" , "sla_calc" ) ) ;
22192233 methods. push_back ( method ( "calculate_sla_view" , false , "none" , "" ) ) ;
@@ -2256,6 +2270,7 @@ impl SLACalculatorContract {
22562270 methods. push_back ( method ( "get_storage_footprint_estimate" , false , "none" , "" ) ) ;
22572271 methods. push_back ( method ( "get_storage_version" , false , "none" , "" ) ) ;
22582272 methods. push_back ( method ( "get_version_info" , false , "none" , "" ) ) ;
2273+ methods. push_back ( method ( "get_version_negotiation_info" , false , "none" , "" ) ) ;
22592274 // Health:
22602275 methods. push_back ( method ( "healthcheck" , false , "none" , "" ) ) ;
22612276 // Init:
@@ -3267,28 +3282,38 @@ impl SLACalculatorContract {
32673282 }
32683283
32693284 fn publish_sla_event ( env : & Env , severity : Symbol , result : & SLAResult ) {
3285+ // Canonical decision field order (#429): shares the SLAResult struct
3286+ // order with set_int and dup_input so indexers parse one layout.
32703287 env. events ( ) . publish (
32713288 ( EVENT_SLA_CALC , EVENT_VERSION , severity) ,
32723289 (
32733290 result. outage_id . clone ( ) ,
32743291 result. status . clone ( ) ,
3275- result. payment_type . clone ( ) ,
3276- result. rating . clone ( ) ,
32773292 result. mttr_minutes ,
32783293 result. threshold_minutes ,
32793294 result. amount ,
3295+ result. payment_type . clone ( ) ,
3296+ result. rating . clone ( ) ,
3297+ result. config_version_hash ,
3298+ result. recorded_at ,
32803299 ) ,
32813300 ) ;
32823301 }
32833302
32843303 fn publish_settlement_intent_event ( env : & Env , severity : Symbol , result : & SLAResult ) {
3304+ // Canonical decision field order (#429) carrying the full decision
3305+ // (mttr_minutes, threshold_minutes, rating) so a settlement-only
3306+ // consumer can reconstruct the SLA decision (#428).
32853307 env. events ( ) . publish (
32863308 ( EVENT_SETTLE_INTENT , EVENT_VERSION , severity) ,
32873309 (
32883310 result. outage_id . clone ( ) ,
32893311 result. status . clone ( ) ,
3290- result. payment_type . clone ( ) ,
3312+ result. mttr_minutes ,
3313+ result. threshold_minutes ,
32913314 result. amount ,
3315+ result. payment_type . clone ( ) ,
3316+ result. rating . clone ( ) ,
32923317 result. config_version_hash ,
32933318 result. recorded_at ,
32943319 ) ,
@@ -3755,6 +3780,45 @@ impl SLACalculatorContract {
37553780 } )
37563781 }
37573782
3783+ // -------------------------------------------------------------------
3784+ // SC-W5-078 – Version negotiation endpoint for multi-contract handshake
3785+ // -------------------------------------------------------------------
3786+
3787+ /// Returns the `VersionNegotiationInfo` for this contract, exposing the
3788+ /// version-negotiation protocol data (`protocol_version`,
3789+ /// `min_compatible_protocol`, storage version, pause & migration state)
3790+ /// over a live contract method.
3791+ ///
3792+ /// This makes the multi-contract handshake documented in
3793+ /// `version_negotiation.rs` and `docs/VERSION_NEGOTIATION_CONTRIBUTOR_GUIDE.md`
3794+ /// actually runnable: a coordinator/backend calls this on each peer to
3795+ /// obtain the `protocol_version`/`min_compatible_protocol` it needs to
3796+ /// feed `negotiate_contract_versions` off-chain (or via a cross-contract
3797+ /// coordinator), instead of the data living only in dead code (#427).
3798+ ///
3799+ /// Like `get_version_info`, this intentionally bypasses `check_version`
3800+ /// so it remains callable even in a pre-migration or pre-init state.
3801+ ///
3802+ /// # Returns
3803+ /// The `VersionNegotiationInfo` for this contract (empty peers: a
3804+ /// coordinator can then run the negotiation rules against a peer list it
3805+ /// assembles from these responses).
3806+ pub fn get_version_negotiation_info (
3807+ env : Env ,
3808+ ) -> Result < crate :: version_negotiation:: VersionNegotiationInfo , SLAError > {
3809+ let stored_version: u32 = env
3810+ . storage ( )
3811+ . instance ( )
3812+ . get ( & STORAGE_VERSION_KEY )
3813+ . ok_or ( SLAError :: NotInitialized ) ?;
3814+ let is_paused: bool = env. storage ( ) . instance ( ) . get ( & PAUSED_KEY ) . unwrap_or ( false ) ;
3815+ Ok ( crate :: version_negotiation:: build_negotiation_info (
3816+ stored_version,
3817+ STORAGE_VERSION ,
3818+ is_paused,
3819+ ) )
3820+ }
3821+
37583822 // -------------------------------------------------------------------
37593823 // #218 – Read-only healthcheck path for backend startup readiness
37603824 // -------------------------------------------------------------------
0 commit comments