feat(tiers): EmbeddedAI on-chain — base_multiplier + subsidy piso EmbeddedArm (#16)

Espejea HardwareTier::EmbeddedAI (Jetson Orin Nano) en el mirror de la cadena,
en lockstep con arkhe-core bbac1b2 (Ergon Fase 1 EmbeddedAI, ADR-0072 / DC 2ba235e1).

Trampa de la fuente preservada byte-fiel: EmbeddedAI APPEND discriminante 4
(SCALE wire-safe, attestations runtime_tier:u8 viejas válidas) PERO rank de
capacidad 1 (entre EmbeddedArm=0 y Portable=2). Por eso Ord es MANUAL por rank()
(no derive — un derive pondría EmbeddedAI > Ultra, falso) y el gating Hero es por
rank, no por discriminante.

- tiers.rs: variant EmbeddedAI (last) + Ord/PartialOrd manual por rank() +
  rank()/ordinal()/from_ordinal() SSOT (el reverse-map que evita perder el
  tier-floor al deserializar, #16) + base_multiplier_x100=10 (piso, el estudiante
  en Jetson no paga más que en Pi 5) + surcharge Hero gated=0 + supports_hero=false.
- fee_schedule.rs: 6 arms EmbeddedAI = piso EmbeddedArm en subsidy_basis_points +
  educational override incluye EmbeddedAI (device de estudiante).
- fees.rs / unified.rs: cobertura — compute_fee(EmbeddedAI)==EmbeddedArm + grid de
  paridad OCAFB-5 extendido a EmbeddedAI.

Tests: 21 suites / 261 workspace verde, 0 regresión. SCALE index == ordinal sellado.
FIX-4 (verificador attestation) ortogonal — no mapea el enum, intacto.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 4.8 committed 2026-06-16 19:24:17 -05:00
1 parent fcf768b046
commit ca50938495
4 files changed
+236 -13

No files matched your search

+36 -4
View File
@@ -87,6 +87,11 @@ pub const fn subsidy_basis_points(hw: HardwareTier, action: ChainAction) -> u32
(EmbeddedArm, Write) => 9000, (EmbeddedArm, Write) => 9000,
(EmbeddedArm, Compute) => 10000, // educational compute free (EmbeddedArm, Compute) => 10000, // educational compute free
(EmbeddedArm, Transfer) => 9000, (EmbeddedArm, Transfer) => 9000,
// EmbeddedAI (Jetson Orin Nano): MISMO piso EmbeddedArm — Inmutable #16,
// el estudiante en Jetson no paga más que en Pi 5. Ergon bbac1b2.
(EmbeddedAI, Write) => 9000,
(EmbeddedAI, Compute) => 10000, // educational compute free
(EmbeddedAI, Transfer) => 9000,
// Portable: subsidy media // Portable: subsidy media
(Portable, Write) => 7500, (Portable, Write) => 7500,
(Portable, Compute) => 5000, (Portable, Compute) => 5000,
@@ -108,6 +113,10 @@ pub const fn subsidy_basis_points(hw: HardwareTier, action: ChainAction) -> u32
(EmbeddedArm, DeployContract) => 9000, // Pi 5 dev contracts canonical (EmbeddedArm, DeployContract) => 9000, // Pi 5 dev contracts canonical
(EmbeddedArm, CallContract) => 10000, // educational call free (EmbeddedArm, CallContract) => 10000, // educational call free
(EmbeddedArm, UploadContractCode) => 9000, // Pi 5 storage subsidy (EmbeddedArm, UploadContractCode) => 9000, // Pi 5 storage subsidy
// EmbeddedAI (Jetson): mismo piso EmbeddedArm en contract actions (#16).
(EmbeddedAI, DeployContract) => 9000, // Jetson dev contracts = piso Pi 5
(EmbeddedAI, CallContract) => 10000, // educational call free
(EmbeddedAI, UploadContractCode) => 9000, // Jetson storage subsidy = piso
(Portable, DeployContract) => 7500, (Portable, DeployContract) => 7500,
(Portable, CallContract) => 5000, (Portable, CallContract) => 5000,
(Portable, UploadContractCode) => 7500, (Portable, UploadContractCode) => 7500,
@@ -247,11 +256,12 @@ impl OitFeeSchedule for OitFeeScheduleMock {
} }
fn subsidy_eligible(&self, _user_id: &str, action: ChainAction) -> bool { fn subsidy_eligible(&self, _user_id: &str, action: ChainAction) -> bool {
// OCAFB-3 override: educational_world → 100% subsidy EmbeddedArm + Portable // OCAFB-3 override: educational_world → 100% subsidy en los tiers floor
// EmbeddedArm + EmbeddedAI + Portable (Jetson = device de estudiante, #16).
if self.educational_world if self.educational_world
&& matches!( && matches!(
self.default_hardware, self.default_hardware,
HardwareTier::EmbeddedArm | HardwareTier::Portable HardwareTier::EmbeddedArm | HardwareTier::EmbeddedAI | HardwareTier::Portable
) )
{ {
return true; return true;
@@ -260,11 +270,11 @@ impl OitFeeSchedule for OitFeeScheduleMock {
} }
fn tier_multiplier(&self, _user_id: &str, action: ChainAction) -> u64 { fn tier_multiplier(&self, _user_id: &str, action: ChainAction) -> u64 {
// Educational override → multiplier 0 (free) // Educational override → multiplier 0 (free) en los tiers floor.
if self.educational_world if self.educational_world
&& matches!( && matches!(
self.default_hardware, self.default_hardware,
HardwareTier::EmbeddedArm | HardwareTier::Portable HardwareTier::EmbeddedArm | HardwareTier::EmbeddedAI | HardwareTier::Portable
) )
{ {
return 0; return 0;
@@ -295,6 +305,7 @@ mod tests {
// Inmutable #37 baseline: Read NEVER charged per HardwareTier // Inmutable #37 baseline: Read NEVER charged per HardwareTier
for hw in [ for hw in [
HardwareTier::EmbeddedArm, HardwareTier::EmbeddedArm,
HardwareTier::EmbeddedAI,
HardwareTier::Portable, HardwareTier::Portable,
HardwareTier::Desktop, HardwareTier::Desktop,
HardwareTier::Ultra, HardwareTier::Ultra,
@@ -303,6 +314,27 @@ mod tests {
} }
} }
#[test]
fn embedded_ai_subsidy_mirrors_embedded_arm_floor_inmutable_16() {
// El estudiante en Jetson (EmbeddedAI) no paga MÁS que en Pi 5
// (EmbeddedArm): piso idéntico en TODA acción. Ergon bbac1b2, #16.
for action in [
ChainAction::Read,
ChainAction::Write,
ChainAction::Compute,
ChainAction::Transfer,
ChainAction::DeployContract,
ChainAction::CallContract,
ChainAction::UploadContractCode,
] {
assert_eq!(
subsidy_basis_points(HardwareTier::EmbeddedAI, action),
subsidy_basis_points(HardwareTier::EmbeddedArm, action),
"EmbeddedAI debe espejar el piso EmbeddedArm en {action:?}"
);
}
}
#[test] #[test]
fn ocafb_3_ultra_pays_full_fee_canonical() { fn ocafb_3_ultra_pays_full_fee_canonical() {
// Ultra: no subsidy except Read // Ultra: no subsidy except Read
+2 -1
View File
@@ -396,8 +396,9 @@ mod tests {
use super::*; use super::*;
use crate::fee_schedule::compute_fee as vendor_compute_fee; use crate::fee_schedule::compute_fee as vendor_compute_fee;
const ALL_HW: [HardwareTier; 4] = [ const ALL_HW: [HardwareTier; 5] = [
HardwareTier::EmbeddedArm, HardwareTier::EmbeddedArm,
HardwareTier::EmbeddedAI,
HardwareTier::Portable, HardwareTier::Portable,
HardwareTier::Desktop, HardwareTier::Desktop,
HardwareTier::Ultra, HardwareTier::Ultra,
+18
View File
@@ -105,6 +105,24 @@ mod tests {
assert_eq!(fee.as_base_units(), Ogc::from_whole(1).as_base_units()); assert_eq!(fee.as_base_units(), Ogc::from_whole(1).as_base_units());
} }
#[test]
fn embedded_ai_pays_same_floor_as_embedded_arm_inmutable_16() {
// El estudiante en Jetson (EmbeddedAI) paga el MISMO piso 0.1× que en
// Pi 5 (EmbeddedArm): mismo base_multiplier (10) + Hero gated (0). #16.
let ai = compute_fee(
Ogc::from_whole(10),
HardwareTier::EmbeddedAI,
AssetQualityTier::Standard,
);
let arm = compute_fee(
Ogc::from_whole(10),
HardwareTier::EmbeddedArm,
AssetQualityTier::Standard,
);
assert_eq!(ai.as_base_units(), arm.as_base_units());
assert_eq!(ai.as_base_units(), Ogc::from_whole(1).as_base_units());
}
#[test] #[test]
fn portable_pays_three_tenths() { fn portable_pays_three_tenths() {
let fee = compute_fee( let fee = compute_fee(
+180 -8
View File
@@ -14,9 +14,14 @@
/// Per Inmutable #16, fees scale inversely with tier so global access is preserved: /// Per Inmutable #16, fees scale inversely with tier so global access is preserved:
/// a Pi 5 user in Sub-Saharan Africa pays 0.1x what a Vision Pro user pays for the /// a Pi 5 user in Sub-Saharan Africa pays 0.1x what a Vision Pro user pays for the
/// same operation. /// same operation.
#[derive( ///
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, /// **`PartialOrd`/`Ord` se implementan MANUALMENTE** (vía [`HardwareTier::rank`]),
)] /// NO derivados — espejo byte-fiel de `arkhe-core::HardwareTier`. Con el APPEND de
/// `EmbeddedAI` (discriminante 4), un `Ord` derivado ordenaría por posición de
/// declaración y pondría `EmbeddedAI > Ultra` — semánticamente FALSO (es un tier
/// floor con-IA). El orden de capacidad vive en `rank()`; el discriminante de wire
/// vive en `ordinal()`. Confundirlos = perder el tier-floor (#16).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] #[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)]
#[derive(parity_scale_codec::DecodeWithMemTracking)] #[derive(parity_scale_codec::DecodeWithMemTracking)]
#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] #[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)]
@@ -29,23 +34,106 @@ pub enum HardwareTier {
Desktop, Desktop,
/// Vision Pro, high-end workstations — multiplier 0.7× (discount). Hero +0.3×. /// Vision Pro, high-end workstations — multiplier 0.7× (discount). Hero +0.3×.
Ultra, Ultra,
/// Jetson Orin Nano class — embedded ARM **con aceleración IA** (Ampere +
/// Tensor Cores) y memoria UNIFICADA. Floor con-IA que complementa
/// `EmbeddedArm` (Pi 5): mismo piso de fee (0.1×, Inmutable #16 — el
/// estudiante en Jetson no paga más que en Pi 5). Hero gated out.
///
/// **APPEND ordinal 4** (serde por-nombre + SCALE wire-safe: ningún ordinal
/// existente cambia, attestations `runtime_tier: u8` viejas válidas). Su
/// **capacidad NO es la del discriminante**: ver [`HardwareTier::rank`] (=1,
/// entre `EmbeddedArm`=0 y `Portable`=2). ADR-0072 / DC `2ba235e1`. Espejo de
/// `arkhe-core` commit `bbac1b2` (Ergon Fase 1 EmbeddedAI).
EmbeddedAI,
}
impl PartialOrd for HardwareTier {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HardwareTier {
/// Orden por **capacidad** ([`HardwareTier::rank`]), no por discriminante de
/// declaración. Espejo de `arkhe-core::HardwareTier`.
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.rank().cmp(&other.rank())
}
} }
impl HardwareTier { impl HardwareTier {
/// Rango de **capacidad** (desacoplado del discriminante de declaración).
/// `EmbeddedAI` (Jetson) se inserta entre `EmbeddedArm` y `Portable`: tiene IA
/// pero su techo de memoria unificada lo limita. Úsese para TODO gating de
/// capacidad (Hero, presupuestos); NUNCA `ordinal()`. Espejo de `arkhe-core`.
#[inline]
pub const fn rank(self) -> u8 {
match self {
HardwareTier::EmbeddedArm => 0,
HardwareTier::EmbeddedAI => 1,
HardwareTier::Portable => 2,
HardwareTier::Desktop => 3,
HardwareTier::Ultra => 4,
}
}
/// Discriminante de declaración (serde por-nombre + SCALE wire-format).
/// **NO es capacidad** — para gating usar [`HardwareTier::rank`]. `EmbeddedAI`
/// = 4 por APPEND (wire-safe: ordinales existentes intactos). Coincide con el
/// índice SCALE del enum (declaración) por construcción. Espejo de `arkhe-core`.
#[inline]
pub const fn ordinal(self) -> u8 {
match self {
HardwareTier::EmbeddedArm => 0,
HardwareTier::Portable => 1,
HardwareTier::Desktop => 2,
HardwareTier::Ultra => 3,
HardwareTier::EmbeddedAI => 4,
}
}
/// Inverso de [`HardwareTier::ordinal`] — la **fuente única** de
/// `u8 ordinal → HardwareTier`. `None` para un ordinal desconocido
/// (forward-compat con futuros APPEND). Todo consumidor que interprete un
/// tier `u8` (p.ej. `runtime_tier` de una attestation) DEBE delegar aquí en
/// vez de duplicar la tabla: una tabla reverse duplicada fue cómo un variant
/// nuevo (`EmbeddedAI`=4) se mapeaba a `None` y perdía el tier-floor al
/// deserializar (Inmutable #16). Sellado por `ordinal_round_trips_all_variants`.
#[inline]
pub const fn from_ordinal(ordinal: u8) -> Option<HardwareTier> {
match ordinal {
0 => Some(HardwareTier::EmbeddedArm),
1 => Some(HardwareTier::Portable),
2 => Some(HardwareTier::Desktop),
3 => Some(HardwareTier::Ultra),
4 => Some(HardwareTier::EmbeddedAI),
_ => None,
}
}
/// Base fee multiplier scaled by 100 (avoids float). /// Base fee multiplier scaled by 100 (avoids float).
/// `EmbeddedArm` = 10 means 0.10× the base fee, `Desktop` = 100 means 1.00×. /// `EmbeddedArm` = 10 means 0.10× the base fee, `Desktop` = 100 means 1.00×.
#[inline] #[inline]
pub const fn base_multiplier_x100(self) -> u32 { pub const fn base_multiplier_x100(self) -> u32 {
match self { match self {
HardwareTier::EmbeddedArm => 10, HardwareTier::EmbeddedArm => 10,
// EmbeddedAI (Jetson): piso EmbeddedArm (0.10×) — Inmutable #16: el
// estudiante en Jetson no paga más que en Pi 5. Ergon `bbac1b2`.
HardwareTier::EmbeddedAI => 10,
HardwareTier::Portable => 30, HardwareTier::Portable => 30,
HardwareTier::Desktop => 100, HardwareTier::Desktop => 100,
HardwareTier::Ultra => 70, HardwareTier::Ultra => 70,
} }
} }
/// True if this tier can transact `AssetQualityTier::Hero` assets. EmbeddedArm /// True if this tier can transact `AssetQualityTier::Hero` assets. EmbeddedArm,
/// and Portable are gated out (Hero requires more VRAM than these tiers ship). /// EmbeddedAI and Portable are gated out (Hero requires more VRAM/unified
/// memory than these tiers ship). El gating es por **capacidad** ([`rank`]):
/// `EmbeddedAI` (rank 1) NO pasa Hero aunque su discriminante sea 4 — espejo
/// de `arkhe-core::HardwareTier::asset_quality_tier` (Hero gated por memoria
/// unificada, no por GPU).
///
/// [`rank`]: HardwareTier::rank
#[inline] #[inline]
pub const fn supports_hero(self) -> bool { pub const fn supports_hero(self) -> bool {
matches!(self, HardwareTier::Desktop | HardwareTier::Ultra) matches!(self, HardwareTier::Desktop | HardwareTier::Ultra)
@@ -75,9 +163,11 @@ impl AssetQualityTier {
(AssetQualityTier::Standard, _) => 0, (AssetQualityTier::Standard, _) => 0,
(AssetQualityTier::Hero, HardwareTier::Desktop) => 50, (AssetQualityTier::Hero, HardwareTier::Desktop) => 50,
(AssetQualityTier::Hero, HardwareTier::Ultra) => 30, (AssetQualityTier::Hero, HardwareTier::Ultra) => 30,
// Hero gated out for EmbeddedArm + Portable — surcharge 0 because the // Hero gated out for EmbeddedArm + EmbeddedAI + Portable (rank < Desktop)
// operation should be rejected upstream by the runtime, not charged. // — surcharge 0 because the operation should be rejected upstream by the
// runtime, not charged.
(AssetQualityTier::Hero, HardwareTier::EmbeddedArm) => 0, (AssetQualityTier::Hero, HardwareTier::EmbeddedArm) => 0,
(AssetQualityTier::Hero, HardwareTier::EmbeddedAI) => 0,
(AssetQualityTier::Hero, HardwareTier::Portable) => 0, (AssetQualityTier::Hero, HardwareTier::Portable) => 0,
} }
} }
@@ -130,6 +220,8 @@ mod tests {
#[test] #[test]
fn multipliers_match_chn_8_canonical() { fn multipliers_match_chn_8_canonical() {
assert_eq!(HardwareTier::EmbeddedArm.base_multiplier_x100(), 10); assert_eq!(HardwareTier::EmbeddedArm.base_multiplier_x100(), 10);
// EmbeddedAI = piso EmbeddedArm (0.10×, Inmutable #16). Ergon bbac1b2.
assert_eq!(HardwareTier::EmbeddedAI.base_multiplier_x100(), 10);
assert_eq!(HardwareTier::Portable.base_multiplier_x100(), 30); assert_eq!(HardwareTier::Portable.base_multiplier_x100(), 30);
assert_eq!(HardwareTier::Desktop.base_multiplier_x100(), 100); assert_eq!(HardwareTier::Desktop.base_multiplier_x100(), 100);
assert_eq!(HardwareTier::Ultra.base_multiplier_x100(), 70); assert_eq!(HardwareTier::Ultra.base_multiplier_x100(), 70);
@@ -138,11 +230,85 @@ mod tests {
#[test] #[test]
fn hero_gating_canonical() { fn hero_gating_canonical() {
assert!(!HardwareTier::EmbeddedArm.supports_hero()); assert!(!HardwareTier::EmbeddedArm.supports_hero());
// EmbeddedAI (rank 1 < Desktop 3) NO pasa Hero, pese a discriminante 4.
assert!(!HardwareTier::EmbeddedAI.supports_hero());
assert!(!HardwareTier::Portable.supports_hero()); assert!(!HardwareTier::Portable.supports_hero());
assert!(HardwareTier::Desktop.supports_hero()); assert!(HardwareTier::Desktop.supports_hero());
assert!(HardwareTier::Ultra.supports_hero()); assert!(HardwareTier::Ultra.supports_hero());
} }
#[test]
fn tier_ordering_by_capability_rank() {
// Ord MANUAL vía rank() — EmbeddedAI se inserta entre EmbeddedArm y
// Portable, NUNCA por encima de Ultra (lo que haría el derive(Ord) roto).
assert!(HardwareTier::EmbeddedArm < HardwareTier::EmbeddedAI);
assert!(HardwareTier::EmbeddedAI < HardwareTier::Portable);
assert!(HardwareTier::Portable < HardwareTier::Desktop);
assert!(HardwareTier::Desktop < HardwareTier::Ultra);
// El anti-test de la trampa: EmbeddedAI (discriminante 4) NO es > Ultra.
assert!(HardwareTier::EmbeddedAI < HardwareTier::Ultra);
// rank explícito.
assert_eq!(HardwareTier::EmbeddedArm.rank(), 0);
assert_eq!(HardwareTier::EmbeddedAI.rank(), 1);
assert_eq!(HardwareTier::Portable.rank(), 2);
assert_eq!(HardwareTier::Desktop.rank(), 3);
assert_eq!(HardwareTier::Ultra.rank(), 4);
}
#[test]
fn ordinal_round_trips_all_variants() {
// SSOT del mapeo u8→tier: from_ordinal(ordinal()) == Some(self) para TODOS
// los variants. Caza el bug #16: un APPEND que olvide from_ordinal mapearía
// el tier nuevo a None y perdería el floor al deserializar.
for t in [
HardwareTier::EmbeddedArm,
HardwareTier::Portable,
HardwareTier::Desktop,
HardwareTier::Ultra,
HardwareTier::EmbeddedAI,
] {
assert_eq!(HardwareTier::from_ordinal(t.ordinal()), Some(t));
}
// Ordinales existentes intactos (attestations viejas runtime_tier: u8).
assert_eq!(HardwareTier::EmbeddedArm.ordinal(), 0);
assert_eq!(HardwareTier::Portable.ordinal(), 1);
assert_eq!(HardwareTier::Desktop.ordinal(), 2);
assert_eq!(HardwareTier::Ultra.ordinal(), 3);
assert_eq!(HardwareTier::EmbeddedAI.ordinal(), 4, "APPEND wire-safe");
// Forward-compat: ordinal desconocido → None.
assert_eq!(HardwareTier::from_ordinal(5), None);
assert_eq!(HardwareTier::from_ordinal(255), None);
}
#[test]
fn embedded_ai_scale_index_matches_ordinal() {
use parity_scale_codec::{Decode, Encode};
// SCALE codifica por índice de declaración: EmbeddedAI declarado último =
// índice 4 = ordinal(). Garantiza que el wire on-chain coincide con el u8.
for t in [
HardwareTier::EmbeddedArm,
HardwareTier::Portable,
HardwareTier::Desktop,
HardwareTier::Ultra,
HardwareTier::EmbeddedAI,
] {
let enc = t.encode();
assert_eq!(enc.len(), 1, "fieldless enum = 1 byte {t:?}");
assert_eq!(enc[0], t.ordinal(), "SCALE index debe = ordinal {t:?}");
let back = HardwareTier::decode(&mut &enc[..]).unwrap();
assert_eq!(back, t);
}
}
#[test]
fn embedded_ai_serde_name_stable() {
// serde por-nombre — wire de payloads guardados estable cross-version.
let s = serde_json::to_string(&HardwareTier::EmbeddedAI).unwrap();
assert_eq!(s, "\"EmbeddedAI\"");
let back: HardwareTier = serde_json::from_str(&s).unwrap();
assert_eq!(back, HardwareTier::EmbeddedAI);
}
#[test] #[test]
fn hero_surcharge_canonical_chn_8() { fn hero_surcharge_canonical_chn_8() {
assert_eq!( assert_eq!(
@@ -159,6 +325,7 @@ mod tests {
fn standard_surcharge_always_zero() { fn standard_surcharge_always_zero() {
for hw in [ for hw in [
HardwareTier::EmbeddedArm, HardwareTier::EmbeddedArm,
HardwareTier::EmbeddedAI,
HardwareTier::Portable, HardwareTier::Portable,
HardwareTier::Desktop, HardwareTier::Desktop,
HardwareTier::Ultra, HardwareTier::Ultra,
@@ -169,11 +336,16 @@ mod tests {
#[test] #[test]
fn hero_surcharge_zero_for_gated_tiers() { fn hero_surcharge_zero_for_gated_tiers() {
// Hero on EmbeddedArm/Portable returns 0 because rejection is upstream. // Hero on EmbeddedArm/EmbeddedAI/Portable returns 0 because rejection is
// upstream (rank < Desktop).
assert_eq!( assert_eq!(
AssetQualityTier::Hero.surcharge_x100(HardwareTier::EmbeddedArm), AssetQualityTier::Hero.surcharge_x100(HardwareTier::EmbeddedArm),
0 0
); );
assert_eq!(
AssetQualityTier::Hero.surcharge_x100(HardwareTier::EmbeddedAI),
0
);
assert_eq!( assert_eq!(
AssetQualityTier::Hero.surcharge_x100(HardwareTier::Portable), AssetQualityTier::Hero.surcharge_x100(HardwareTier::Portable),
0 0