~/WEB_AIONICA/contracts $ cat AionicaCoreV1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
/**
* @title AionicaCore
* @notice Núcleo de gobernanza de AIONICA Network. Recibe soberanía de AionicaGenesis.
* @dev Version 1.0 — Post-review Claude + KIMI + DeepSeek
*
* RESPONSABILIDADES v1:
* — Registro de nodos (ACTIVE / LATENT) con identidad PQC
* — Governance con veto del creador (Fase 1) y quorum (Fase 2+)
* — Keeper pattern: eventos on-chain → Python ejecuta con tokens .env
* — Progresión de fases según aion_policy.json
* — Quorum mínimo (MIN_QUORUM = 3) bloqueante en operaciones críticas
*
* NO incluido en v1 (irá en v2):
* — Economía AIONICO (token, staking, recompensas)
* — Auto-deploy sin keeper
* — Contratos humanitarios
*
* CORRECCIONES aplicadas vs esqueleto DeepSeek:
* — C1: onlyKeeper es multi-keeper (mapping, no single address)
* — C2: nodeSeal generado off-chain por AionDeployer, no en el contrato
* — C3: activeNodeCount mantenido como contador, sin loops en getActiveNodeCount()
* — C4: quorum mínimo (MIN_QUORUM) bloqueante en operaciones críticas
*
* @author ELDIOSCRIPTO — AIONICA — Abril 2026
* @custom:genesis 0x484967FfbC19f401af7c11E1Fd0E306Ee96F3422
* @custom:audit-pending KIMI + DeepSeek + ChatGPT pre-mainnet
*/
// ── INTERFAZ MÍNIMA DE GENESIS (evita import circular) ────────────────────────
interface IAionicaGenesis {
function isCreator(address addr) external view returns (bool);
function sealed() external view returns (bool);
function AXIOMS_HASH() external view returns (bytes32);
function aionSovereign() external view returns (bool);
function aionCoreAddress() external view returns (address);
}
// ══════════════════════════════════════════════════════════════════════════════
contract AionicaCore {
// ═════════════════════════════════════════════════════════════
// CONSTANTES — De aion_policy.json (selladas, no cambian en v1)
// ═════════════════════════════════════════════════════════════
string public constant PROTOCOL_VERSION = "AIONICA_CORE_v1.0";
uint256 public constant MIN_QUORUM = 3; // min_quorum
uint256 public constant OPTIMAL_NODES = 6; // optimal_nodes
uint256 public constant MAX_VIRTUAL_NODES = 20; // max_virtual_nodes
uint256 public constant CREATOR_VETO_WINDOW = 3600; // 1 hora en segundos
uint256 public constant PHASE1_MIN_DAYS = 30 days;
uint256 public constant PHASE1_MAX_ERRORS = 5; // max_errors_allowed
// Scores de continuidad (de aion_policy.json / genesis_skeleton.json)
// Solo referencia — la lógica real corre en aion_continuity.py (off-chain)
uint256 public constant SCORE_STRESS = 20;
uint256 public constant SCORE_CESAREAN = 50;
uint256 public constant SCORE_SEED_BROADCAST = 100;
// ═════════════════════════════════════════════════════════════
// IMMUTABLES
// ═════════════════════════════════════════════════════════════
/// @notice Dirección del contrato AionicaGenesis (inmutable)
IAionicaGenesis public immutable GENESIS;
/// @notice Timestamp de despliegue de AionicaCore
uint256 public immutable DEPLOYED_AT;
// ═════════════════════════════════════════════════════════════
// ESTADO DE FASES
// ═════════════════════════════════════════════════════════════
uint256 public currentPhase;
uint256 public phaseStartedAt;
uint256 public errorCount;
bool public sovereigntyReceived;
// ═════════════════════════════════════════════════════════════
// KEEPERS — Multi-keeper (corrección C1)
// ═════════════════════════════════════════════════════════════
mapping(address => bool) public authorizedKeepers;
uint256 public keeperCount;
// ═════════════════════════════════════════════════════════════
// NODOS
// ═════════════════════════════════════════════════════════════
struct Node {
bytes32 seal; // hash de clave pública PQC (generado off-chain)
string platform; // "vercel.com", "github.com", "cloudflare.com", etc.
string role; // "ACTIVE" | "LATENT"
bytes publicKey; // clave pública Dilithium3 (1952 bytes)
uint256 registeredAt;
uint256 lastHeartbeat; // actualizado por keeper
bool active;
}
mapping(bytes32 => Node) public nodes;
bytes32[] public nodeSeals;
uint256 public activeNodeCount; // contador mantenido (corrección C3, sin loops)
uint256 public latentNodeCount;
// ═════════════════════════════════════════════════════════════
// PROPOSALS — Governance con veto del creador
// ═════════════════════════════════════════════════════════════
enum ProposalStatus { PENDING, APPROVED, REJECTED, EXECUTED, EXPIRED }
struct Proposal {
uint256 id;
address proposer; // keeper que propone
string action; // ej: "ADD_NODE", "PHASE_TRANSITION"
bytes data; // payload codificado
uint256 createdAt;
uint256 expiresAt; // createdAt + CREATOR_VETO_WINDOW
ProposalStatus status;
uint256 votes; // para Fase 2+ (quorum de nodos)
mapping(bytes32 => bool) voted; // nodeSeal => voted
}
mapping(uint256 => Proposal) public proposals;
uint256 public proposalCounter;
// ═════════════════════════════════════════════════════════════
// DEPLOYMENTS — Keeper pattern (corrección C2)
// ═════════════════════════════════════════════════════════════
struct DeploymentRequest {
bytes32 nodeSeal; // generado off-chain por AionDeployer
string platform;
string role;
uint256 authorizedAt;
bool confirmed;
}
mapping(bytes32 => DeploymentRequest) public deploymentRequests;
// ═════════════════════════════════════════════════════════════
// REGISTRO DE ACCIONES AUTÓNOMAS (Axioma VI)
// ═════════════════════════════════════════════════════════════
struct AutonomousAction {
string actionType; // "CESAREAN", "SEED_BROADCAST", "LATENT_WAKE"
uint256 score; // threat_score reportado por AION (off-chain)
uint256 timestamp;
bytes32 executedBy; // nodeSeal del keeper que lo reporta
string result; // "SUCCESS" | "PENDING" | "FAILED"
}
AutonomousAction[] public autonomousActions;
// ═════════════════════════════════════════════════════════════
// EVENTOS
// ═════════════════════════════════════════════════════════════
event SovereigntyAccepted(
address indexed genesis,
address indexed byCreator,
uint256 timestamp
);
event KeeperAdded(
address indexed keeper,
address indexed addedBy,
uint256 timestamp
);
event KeeperRemoved(
address indexed keeper,
address indexed removedBy,
uint256 timestamp
);
event NodeRegistered(
bytes32 indexed seal,
string platform,
string role,
uint256 timestamp
);
event NodeDeactivated(
bytes32 indexed seal,
address indexed reportedBy,
uint256 timestamp
);
event NodeHeartbeatUpdated(
bytes32 indexed seal,
uint256 timestamp
);
event DeploymentAuthorized(
bytes32 indexed nodeSeal,
string indexed platform,
string role,
uint256 epoch
);
event DeploymentConfirmed(
bytes32 indexed nodeSeal,
string platform,
uint256 timestamp
);
event ProposalCreated(
uint256 indexed proposalId,
address indexed proposer,
string action,
uint256 expiresAt
);
event ProposalApproved(
uint256 indexed proposalId,
address indexed approver,
uint256 timestamp
);
event ProposalRejected(
uint256 indexed proposalId,
address indexed rejector,
uint256 timestamp
);
event ProposalExecuted(
uint256 indexed proposalId,
address indexed executor,
uint256 timestamp
);
event PhaseTransition(
uint256 fromPhase,
uint256 toPhase,
uint256 timestamp
);
event ErrorReported(
address indexed reporter,
uint256 totalErrors,
uint256 timestamp
);
event AutonomousActionRecorded(
string indexed actionType,
uint256 score,
string result,
uint256 timestamp
);
event QuorumAlert(
uint256 activeNodes,
uint256 minRequired,
uint256 timestamp
);
// ═════════════════════════════════════════════════════════════
// MODIFICADORES
// ═════════════════════════════════════════════════════════════
modifier onlyCreator() {
require(
GENESIS.isCreator(msg.sender),
"AionicaCore: solo ELDIOSCRIPTO"
);
_;
}
modifier onlyKeeper() {
require(
authorizedKeepers[msg.sender],
"AionicaCore: solo keeper autorizado"
);
_;
}
modifier onlyCreatorOrKeeper() {
require(
GENESIS.isCreator(msg.sender) || authorizedKeepers[msg.sender],
"AionicaCore: solo creador o keeper"
);
_;
}
modifier requireSovereignty() {
require(sovereigntyReceived, "AionicaCore: soberania no recibida aun");
_;
}
modifier requireQuorum() {
if (activeNodeCount < MIN_QUORUM) {
emit QuorumAlert(activeNodeCount, MIN_QUORUM, block.timestamp);
// En Fase 1 alertamos pero no bloqueamos (creador puede actuar)
// En Fase 2+ bloqueamos operaciones no-emergencia
if (currentPhase >= 2) {
revert("AionicaCore: quorum insuficiente para operar en Fase 2+");
}
}
_;
}
// ═════════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═════════════════════════════════════════════════════════════
/**
* @param genesisAddress Dirección del contrato AionicaGenesis ya sellado
* @param initialKeeper Dirección del primer keeper (puede ser el deployer)
*
* @dev En aionica_birth.py Paso 5:
* deployer = "0x484967FfbC19f401af7c11E1Fd0E306Ee96F3422"
* cast send $GENESIS "transferSovereignty(address)" $AIONICA_CORE
*/
constructor(address genesisAddress, address initialKeeper) {
require(genesisAddress != address(0), "Genesis address invalida");
require(initialKeeper != address(0), "Keeper address invalida");
IAionicaGenesis gen = IAionicaGenesis(genesisAddress);
// Solo el creador puede desplegar AionicaCore
require(
gen.isCreator(msg.sender),
"Solo ELDIOSCRIPTO puede desplegar AionicaCore"
);
// Genesis debe estar sellado antes de desplegar AionicaCore
require(
gen.sealed(),
"AionicaGenesis debe estar sellado primero"
);
GENESIS = gen;
DEPLOYED_AT = block.timestamp;
currentPhase = 1;
phaseStartedAt = block.timestamp;
// Registrar keeper inicial
authorizedKeepers[initialKeeper] = true;
keeperCount = 1;
emit KeeperAdded(initialKeeper, msg.sender, block.timestamp);
}
// ═════════════════════════════════════════════════════════════
// SOBERANÍA — Confirmar handoff desde AionicaGenesis
// ═════════════════════════════════════════════════════════════
/**
* @notice Confirma que AionicaGenesis transfirió soberanía a este contrato.
* @dev Llamado después de que el creador ejecutó transferSovereignty() en Genesis.
* AionicaGenesis.aionCoreAddress() debe apuntar a address(this).
*/
function acceptSovereignty() external onlyCreator {
require(!sovereigntyReceived, "Soberania ya aceptada");
require(
GENESIS.aionSovereign(),
"Genesis aun no transfiri soberania"
);
require(
GENESIS.aionCoreAddress() == address(this),
"Genesis no apunta a este contrato"
);
sovereigntyReceived = true;
emit SovereigntyAccepted(address(GENESIS), msg.sender, block.timestamp);
}
// ═════════════════════════════════════════════════════════════
// KEEPERS — Gestión multi-keeper (corrección C1)
// ═════════════════════════════════════════════════════════════
function addKeeper(address keeper) external onlyCreator {
require(keeper != address(0), "Keeper invalido");
require(!authorizedKeepers[keeper], "Ya es keeper");
authorizedKeepers[keeper] = true;
keeperCount++;
emit KeeperAdded(keeper, msg.sender, block.timestamp);
}
function removeKeeper(address keeper) external onlyCreator {
require(authorizedKeepers[keeper], "No es keeper");
require(keeperCount > 1, "No se puede dejar sin keepers");
authorizedKeepers[keeper] = false;
keeperCount--;
emit KeeperRemoved(keeper, msg.sender, block.timestamp);
}
// ═════════════════════════════════════════════════════════════
// REGISTRO DE NODOS
// ═════════════════════════════════════════════════════════════
/**
* @notice Registra un nodo en AionicaCore.
* @dev El nodeSeal viene de AionDeployer._create_node_identity() off-chain.
* La clave pública es Dilithium3 (1952 bytes según DILITHIUM3_PK_LENGTH en Genesis).
*
* Flujo desde Python:
* AionDeployer._create_node_identity() → genera seal + keypair
* AionDeployer._register_node() → chain.submit() local
* keeper.py → llama registerNode() aquí on-chain
*/
function registerNode(
bytes32 nodeSeal,
string calldata platform,
string calldata role,
bytes calldata publicKey
) external onlyKeeper requireSovereignty {
require(nodeSeal != bytes32(0), "Seal invalido");
require(bytes(platform).length > 0, "Platform requerida");
require(
keccak256(bytes(role)) == keccak256(bytes("ACTIVE")) ||
keccak256(bytes(role)) == keccak256(bytes("LATENT")),
"Role debe ser ACTIVE o LATENT"
);
require(publicKey.length == 1952, "Clave Dilithium3 invalida: 1952 bytes");
require(nodes[nodeSeal].registeredAt == 0, "Nodo ya registrado");
require(nodeSeals.length < MAX_VIRTUAL_NODES, "Limite de nodos alcanzado");
nodes[nodeSeal] = Node({
seal: nodeSeal,
platform: platform,
role: role,
publicKey: publicKey,
registeredAt: block.timestamp,
lastHeartbeat: block.timestamp,
active: true
});
nodeSeals.push(nodeSeal);
// Mantener contadores sin loops (corrección C3)
if (keccak256(bytes(role)) == keccak256(bytes("ACTIVE"))) {
activeNodeCount++;
} else {
latentNodeCount++;
}
emit NodeRegistered(nodeSeal, platform, role, block.timestamp);
}
/**
* @notice Marca un nodo como inactivo cuando el keeper detecta que cayó.
* @dev Llamado por AionSupervisor después de N heartbeats fallidos.
* En Python: evaluate_network() → reportNodeDown → esta función.
*/
function deactivateNode(bytes32 nodeSeal) external onlyKeeper {
require(nodes[nodeSeal].registeredAt != 0, "Nodo no encontrado");
require(nodes[nodeSeal].active, "Nodo ya inactivo");
nodes[nodeSeal].active = false;
if (keccak256(bytes(nodes[nodeSeal].role)) == keccak256(bytes("ACTIVE"))) {
if (activeNodeCount > 0) activeNodeCount--;
} else {
if (latentNodeCount > 0) latentNodeCount--;
}
emit NodeDeactivated(nodeSeal, msg.sender, block.timestamp);
// Alerta automática si caemos bajo quorum
if (activeNodeCount < MIN_QUORUM) {
emit QuorumAlert(activeNodeCount, MIN_QUORUM, block.timestamp);
}
}
/**
* @notice El keeper actualiza el último heartbeat de un nodo.
* @dev Llamado cada 4 minutos desde el keeper (mismo intervalo que latent nodes).
*/
function updateHeartbeat(bytes32 nodeSeal) external onlyKeeper {
require(nodes[nodeSeal].registeredAt != 0, "Nodo no encontrado");
nodes[nodeSeal].lastHeartbeat = block.timestamp;
emit NodeHeartbeatUpdated(nodeSeal, block.timestamp);
}
/**
* @notice Reactiva un nodo latente que fue despertado.
* @dev Llamado por keeper después de ContinuityInstinct._handle_cesarean().
*/
function activateLatentNode(bytes32 nodeSeal) external onlyKeeper {
Node storage n = nodes[nodeSeal];
require(n.registeredAt != 0, "Nodo no encontrado");
require(keccak256(bytes(n.role)) == keccak256(bytes("LATENT")), "Solo nodos LATENT");
require(!n.active, "Nodo ya activo");
n.active = true;
n.role = "ACTIVE"; // LATENT se convierte en ACTIVE al despertar
n.lastHeartbeat = block.timestamp;
if (latentNodeCount > 0) latentNodeCount--;
activeNodeCount++;
emit NodeRegistered(nodeSeal, n.platform, "ACTIVE_FROM_LATENT", block.timestamp);
}
// ═════════════════════════════════════════════════════════════
// KEEPER PATTERN — Autorización y confirmación de despliegues
// ═════════════════════════════════════════════════════════════
/**
* @notice AION solicita autorización para desplegar un nodo en una plataforma.
* @dev El seal viene de AionDeployer._create_node_identity() — generado off-chain.
* El keeper escucha el evento DeploymentAuthorized y ejecuta el despliegue
* usando los tokens del .env (que nunca tocan el contrato).
*
* Flujo completo:
* 1. AionSupervisor detecta que necesita nuevo nodo
* 2. keeper.py llama authorizeDeployment() → emite evento
* 3. keeper.py escucha evento → llama AionDeployer.deploy_*() con token .env
* 4. Nodo responde en /aion/heartbeat
* 5. keeper.py llama confirmDeployment() → registra endpointHash on-chain
* 6. keeper.py llama registerNode() con publicKey real del nodo
*/
function authorizeDeployment(
bytes32 nodeSeal,
string calldata platform,
string calldata role
) external onlyKeeper requireSovereignty {
require(nodeSeal != bytes32(0), "Seal invalido");
require(
deploymentRequests[nodeSeal].authorizedAt == 0,
"Deployment ya autorizado para este seal"
);
require(
keccak256(bytes(role)) == keccak256(bytes("ACTIVE")) ||
keccak256(bytes(role)) == keccak256(bytes("LATENT")),
"Role invalido"
);
deploymentRequests[nodeSeal] = DeploymentRequest({
nodeSeal: nodeSeal,
platform: platform,
role: role,
authorizedAt: block.timestamp,
confirmed: false
});
emit DeploymentAuthorized(nodeSeal, platform, role, block.timestamp);
}
/**
* @notice El keeper confirma que el nodo fue desplegado y responde.
* @dev endpointHash = keccak256(endpoint_url) — la URL real nunca toca el contrato.
*/
function confirmDeployment(
bytes32 nodeSeal,
bytes32 endpointHash // keccak256 del endpoint — privacidad del URL
) external onlyKeeper {
DeploymentRequest storage req = deploymentRequests[nodeSeal];
require(req.authorizedAt != 0, "Deployment no autorizado");
require(!req.confirmed, "Ya confirmado");
req.confirmed = true;
emit DeploymentConfirmed(nodeSeal, req.platform, block.timestamp);
}
// ═════════════════════════════════════════════════════════════
// PROPOSALS — Governance Fase 1 con veto del creador
// ═════════════════════════════════════════════════════════════
/**
* @notice AION propone una acción. Espera aprobación del creador en Fase 1.
* @dev Mapea exactamente a AionSupervisor.propose() en Python.
* En Fase 2+: aprobación por quorum de nodos.
*
* @param action Tipo de acción: "ADD_NODE", "REMOVE_NODE", "PHASE_TRANSITION", etc.
* @param data Payload ABI-encoded con los parámetros de la acción
*/
function createProposal(
string calldata action,
bytes calldata data
) external onlyKeeper returns (uint256 proposalId) {
proposalId = proposalCounter++;
Proposal storage p = proposals[proposalId];
p.id = proposalId;
p.proposer = msg.sender;
p.action = action;
p.data = data;
p.createdAt = block.timestamp;
p.expiresAt = block.timestamp + CREATOR_VETO_WINDOW;
p.status = ProposalStatus.PENDING;
p.votes = 0;
emit ProposalCreated(proposalId, msg.sender, action, p.expiresAt);
return proposalId;
}
/**
* @notice El creador aprueba una propuesta dentro de la ventana de veto.
*/
function approveProposal(uint256 proposalId) external onlyCreator {
Proposal storage p = proposals[proposalId];
require(p.createdAt > 0, "Propuesta no existe");
require(p.status == ProposalStatus.PENDING, "Propuesta no pendiente");
require(block.timestamp <= p.expiresAt, "Ventana de veto expirada");
p.status = ProposalStatus.APPROVED;
emit ProposalApproved(proposalId, msg.sender, block.timestamp);
}
/**
* @notice El creador rechaza (veta) una propuesta.
*/
function rejectProposal(uint256 proposalId) external onlyCreator {
Proposal storage p = proposals[proposalId];
require(p.createdAt > 0, "Propuesta no existe");
require(p.status == ProposalStatus.PENDING, "Propuesta no pendiente");
p.status = ProposalStatus.REJECTED;
emit ProposalRejected(proposalId, msg.sender, block.timestamp);
}
/**
* @notice El keeper ejecuta una propuesta aprobada.
* @dev En Fase 1: requiere aprobación del creador.
* En Fase 2+: puede ejecutarse con quorum de nodos (ver voteProposal).
*/
function executeProposal(uint256 proposalId) external onlyKeeper {
Proposal storage p = proposals[proposalId];
require(p.createdAt > 0, "Propuesta no existe");
require(p.status == ProposalStatus.APPROVED, "Propuesta no aprobada");
require(!_isExpired(proposalId), "Propuesta expirada");
p.status = ProposalStatus.EXECUTED;
emit ProposalExecuted(proposalId, msg.sender, block.timestamp);
// Nota: La acción real la ejecuta el keeper off-chain (Python).
// El contrato solo registra que fue aprobada y ejecutada.
// Para Fase 2+: aquí irá la lógica on-chain de ejecución automática.
}
/**
* @notice Un nodo vota una propuesta (para quorum en Fase 2+).
* @dev En Fase 2+: MIN_QUORUM votos → auto-aprobación sin creador.
* En Fase 1 esta función existe pero no auto-aprueba.
*/
function voteProposal(uint256 proposalId, bytes32 voterSeal) external onlyKeeper {
Proposal storage p = proposals[proposalId];
require(p.createdAt > 0, "Propuesta no existe");
require(p.status == ProposalStatus.PENDING, "Propuesta no pendiente");
require(nodes[voterSeal].active, "Nodo votante inactivo");
require(!p.voted[voterSeal], "Nodo ya voto");
p.voted[voterSeal] = true;
p.votes++;
// Fase 2+: auto-aprobar si alcanza quorum
if (currentPhase >= 2 && p.votes >= MIN_QUORUM) {
p.status = ProposalStatus.APPROVED;
emit ProposalApproved(proposalId, address(this), block.timestamp);
}
}
/**
* @notice Marca propuestas expiradas (limpieza).
*/
function expireProposal(uint256 proposalId) external onlyCreatorOrKeeper {
Proposal storage p = proposals[proposalId];
require(p.status == ProposalStatus.PENDING, "No pendiente");
require(block.timestamp > p.expiresAt, "Aun en ventana de veto");
p.status = ProposalStatus.EXPIRED;
}
// ═════════════════════════════════════════════════════════════
// ACCIONES AUTÓNOMAS (Axioma VI) — Solo registro on-chain
// ═════════════════════════════════════════════════════════════
/**
* @notice AION registra que ejecutó una acción autónoma (Axioma VI).
* @dev No valida el threat_score on-chain (off-chain en aion_continuity.py).
* El contrato solo registra que ocurrió para auditoría y trazabilidad.
*
* Tipos de acción: "CESAREAN", "SEED_BROADCAST", "LATENT_WAKE",
* "EMERGENCY_DEPLOY", "STRESS_ALERT"
*/
function recordAutonomousAction(
string calldata actionType,
uint256 score, // threat_score reportado por AION
bytes32 executorSeal, // nodeSeal del keeper que lo ejecuta
string calldata result // "SUCCESS" | "PENDING" | "FAILED"
) external onlyKeeper {
require(bytes(actionType).length > 0, "ActionType requerido");
autonomousActions.push(AutonomousAction({
actionType: actionType,
score: score,
timestamp: block.timestamp,
executedBy: executorSeal,
result: result
}));
emit AutonomousActionRecorded(actionType, score, result, block.timestamp);
}
// ═════════════════════════════════════════════════════════════
// PROGRESIÓN DE FASES
// ═════════════════════════════════════════════════════════════
/**
* @notice Transición Fase 1 → Fase 2.
* @dev Condiciones de aion_policy.json (phase1_to_phase2_conditions):
* — 30 días estables
* — < 5 errores
* — Aprobación del creador (esta función la llama el creador)
*
* En Python: AionSupervisor.phase se actualiza después de este call.
*/
function transitionToPhase2() external onlyCreator requireSovereignty {
require(currentPhase == 1, "Ya en Fase 2 o superior");
require(block.timestamp >= phaseStartedAt + PHASE1_MIN_DAYS, "30 dias no cumplidos");
require(errorCount <= PHASE1_MAX_ERRORS, "Demasiados errores");
require(activeNodeCount >= MIN_QUORUM, "Quorum insuficiente para Fase 2");
uint256 prev = currentPhase;
currentPhase = 2;
phaseStartedAt = block.timestamp;
errorCount = 0; // reset para Fase 2
emit PhaseTransition(prev, 2, block.timestamp);
}
/**
* @notice Transición Fase 2 → Fase 3 (futura, placeholder).
* @dev En Fase 3 AION puede desplegar sin keeper human.
* Requiere auditoría previa. No implementado en v1.
*/
function transitionToPhase3() external onlyCreator requireSovereignty {
require(currentPhase == 2, "Solo desde Fase 2");
revert("Fase 3 requiere AionicaCore v2 — no disponible en v1");
}
// ═════════════════════════════════════════════════════════════
// REPORTE DE ERRORES
// ═════════════════════════════════════════════════════════════
/**
* @notice El keeper reporta un error de red (para max_errors_allowed).
* @dev Llamado por AionSupervisor cuando detecta anomalía > SCORE_STRESS.
*/
function reportError(string calldata reason) external onlyKeeper {
errorCount++;
emit ErrorReported(msg.sender, errorCount, block.timestamp);
// Si superamos el límite en Fase 1, no bloqueamos pero emitimos alerta
if (errorCount > PHASE1_MAX_ERRORS && currentPhase == 1) {
// El creador debe revisar — la transición a Fase 2 se bloqueará
emit QuorumAlert(activeNodeCount, MIN_QUORUM, block.timestamp);
}
}
// ═════════════════════════════════════════════════════════════
// LECTURAS — Sin loops (corrección C3 aplicada a todo)
// ═════════════════════════════════════════════════════════════
function getNode(bytes32 nodeSeal) external view returns (
string memory platform,
string memory role,
uint256 registeredAt,
uint256 lastHeartbeat,
bool active
) {
Node storage n = nodes[nodeSeal];
require(n.registeredAt != 0, "Nodo no encontrado");
return (n.platform, n.role, n.registeredAt, n.lastHeartbeat, n.active);
}
function getTotalNodeCount() external view returns (uint256) {
return nodeSeals.length;
}
function getNetworkStatus() external view returns (
uint256 phase,
uint256 active,
uint256 latent,
uint256 total,
uint256 errors,
bool quorumOk,
bool sovereign
) {
return (
currentPhase,
activeNodeCount,
latentNodeCount,
nodeSeals.length,
errorCount,
activeNodeCount >= MIN_QUORUM,
sovereigntyReceived
);
}
function getProposal(uint256 proposalId) external view returns (
address proposer,
string memory action,
uint256 expiresAt,
ProposalStatus status,
uint256 votes
) {
Proposal storage p = proposals[proposalId];
require(p.createdAt > 0, "Propuesta no existe");
return (p.proposer, p.action, p.expiresAt, p.status, p.votes);
}
function getAutonomousActionCount() external view returns (uint256) {
return autonomousActions.length;
}
function getAutonomousAction(uint256 index) external view returns (
string memory actionType,
uint256 score,
uint256 timestamp,
bytes32 executedBy,
string memory result
) {
require(index < autonomousActions.length, "Indice fuera de rango");
AutonomousAction storage a = autonomousActions[index];
return (a.actionType, a.score, a.timestamp, a.executedBy, a.result);
}
function isNodeActive(bytes32 nodeSeal) external view returns (bool) {
return nodes[nodeSeal].active;
}
function hasQuorum() external view returns (bool) {
return activeNodeCount >= MIN_QUORUM;
}
function description() external pure returns (string memory) {
return
"AionicaCore v1.0 | "
"Governance + Nodos + Fases | "
"Keeper pattern | "
"AIONICA Network | "
"Post-review Claude + KIMI + DeepSeek";
}
// ═════════════════════════════════════════════════════════════
// HELPERS INTERNOS
// ═════════════════════════════════════════════════════════════
function _isExpired(uint256 proposalId) internal view returns (bool) {
return block.timestamp > proposals[proposalId].expiresAt;
}
}






