~/WEB_AIONICA/contracts $ cat AionicaCoreV1.sol

// SPDX License Identifier: MIT

pragma solidity ^0.8.29;

/**

* @title AionicaCore

* @notice AIONICA Network governance core. Receives sovereignty from AionicaGenesis.

* @dev Version 1.0 — Post-review Claude + KIMI + DeepSeek

*

* RESPONSIBILITIES v1:

* — Node registration (ACTIVE / LATENT) with PQC identity

* — Governance with creator veto (Phase 1) and quorum (Phase 2+)

* — Keeper pattern: eventos on-chain → Python ejecuta con tokens .env

* — Phase progression according to aion_policy.json

* — Minimum quorum (MIN_QUORUM = 3) blocking in critical operations

*

* NOT included in v1 (will be in v2):

* — Aionic Economy (token, staking, rewards)

* — Auto-deploy sin keeper

* — Humanitarian contracts

*

* Corrections applied vs. DeepSeek skeleton:

* — C1: onlyKeeper es multi-keeper (mapping, no single address)

* — C2: nodeSeal generated off-chain by AionDeployer, not in the contract

* — C3: activeNodeCount kept as a counter, no loops in getActiveNodeCount()

* — C4: minimum quorum (MIN_QUORUM) blocking in critical operations

*

* @author ELDIOSCRIPTO — AIONICA — April 2026

* @custom:genesis 0x484967FfbC19f401af7c11E1Fd0E306Ee96F3422

* @custom:audit-pending KIMI + DeepSeek + ChatGPT pre-mainnet

*/

// ── MINIMAL GENESIS INTERFACE (avoids circular imports) ────────────────────────

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 {

// ═════════════════════════════════════════════════════════════

// CONSTANTS — From aion_policy.json (sealed, do not change in 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 hour in seconds

uint256 public constant PHASE1_MIN_DAYS = 30 days;

uint256 public constant PHASE1_MAX_ERRORS = 5; // max_errors_allowed

// Continuity scores (from aion_policy.json / genesis_skeleton.json)

// Reference only — the actual logic runs in aion_continuity.py (off-chain)

uint256 public constant SCORE_STRESS = 20;

uint256 public constant SCORE_CESAREAN = 50;

uint256 public constant SCORE_SEED_BROADCAST = 100;

// ═════════════════════════════════════════════════════════════

// IMMUTABLE

// ═════════════════════════════════════════════════════════════

/// @notice AionicaGenesis contract address (immutable)

IAionicaGenesis public immutable GENESIS;

/// @notice AionicaCore Deployment Timestamp

uint256 public immutable DEPLOYED_AT;

// ═════════════════════════════════════════════════════════════

// STATE OF PHASE

// ═════════════════════════════════════════════════════════════

uint256 public currentPhase;

uint256 public phaseStartedAt;

uint256 public errorCount;

bool public sovereigntyReceived;

// ═════════════════════════════════════════════════════════════

// KEEPERS — Multi-keeper (C1 correction)

// ═════════════════════════════════════════════════════════════

mapping(address => bool) public authorizedKeepers;

uint256 public keeperCount;

// ═════════════════════════════════════════════════════════════

// NODES

// ═════════════════════════════════════════════════════════════

struct Node {

bytes32 seal; // PQC public key hash (generated off-chain)

string platform; // "vercel.com", "github.com", "cloudflare.com", etc.

string role; // "ACTIVE" | "LATENT"

bytes publicKey; // public key Dilithium3 (1952 bytes)

uint256 registeredAt;

uint256 lastHeartbeat; // actualizado por keeper

bool active;

}

mapping(bytes32 => Node) public nodes;

bytes32[] public nodeSeals;

public activeNodeCount; // maintained counter (C3 correction, no loops)

uint256 public latentNodeCount;

// ═════════════════════════════════════════════════════════════

// PROPOSALS — Governance with creator veto

// ═════════════════════════════════════════════════════════════

enum ProposalStatus { PENDING, APPROVED, REJECTED, EXECUTED, EXPIRED }

struct Proposal {

uint256 id;

address to propose; // keeper that proposes

string action; // ej: "ADD_NODE", "PHASE_TRANSITION"

bytes data; // encoded payload

uint256 createdAt;

uint256 expiresAt; // createdAt + CREATOR_VETO_WINDOW

ProposalStatus status;

uint256 votes; // for Phase 2+ (node ​​quorum)

mapping(bytes32 => bool) voted; // nodeSeal => voted

}

mapping(uint256 => Proposal) public proposals;

uint256 public proposalCounter;

// ═════════════════════════════════════════════════════════════

// DEPLOYMENTS — Keeper pattern (correction C2)

// ═════════════════════════════════════════════════════════════

struct DeploymentRequest {

bytes32 nodeSeal; // generado off-chain por AionDeployer

string platform;

string role;

uint256 authorizedAt;

bool confirmed;

}

mapping(bytes32 => DeploymentRequest) public deploymentRequests;

// ═════════════════════════════════════════════════════════════

// REGISTRATION OF AUTONOMOUS ACTIONS (Axiom VI)

// ═════════════════════════════════════════════════════════════

struct AutonomousAction {

string actionType; // "CESAREAN", "SEED_BROADCAST", "LATENT_WAKE"

uint256 score; // threat_score reportado por AION (off-chain)

uint256 timestamp;

bytes32 executedBy; // nodeSeal of the keeper that reports it

string result; // "SUCCESS" | "PENDING" | "FAILED"

}

AutonomousAction[] public autonomousActions;

// ═════════════════════════════════════════════════════════════

Events

// ═════════════════════════════════════════════════════════════

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

);

// ═════════════════════════════════════════════════════════════

// MODIFIERS

// ═════════════════════════════════════════════════════════════

modifier onlyCreator() {

require(

GENESIS.isCreator(msg.sender),

"AionicaCore: only ELDIOSCRIPTO"

);

_;

}

modifier onlyKeeper() {

require(

authorizedKeepers[msg.sender],

"AionicaCore: authorized solo keeper"

);

_;

}

modifier onlyCreatorOrKeeper() {

require(

GENESIS.isCreator(msg.sender) || authorizedKeepers[msg.sender],

"AionicaCore: creator or keeper only"

);

_;

}

modifier requireSovereignty() {

require(sovereigntyReceived, "AionicaCore: sovereignty not yet received");

_;

}

modifier requireQuorum() {

if (activeNodeCount < MIN_QUORUM) {

emit QuorumAlert(activeNodeCount, MIN_QUORUM, block.timestamp);

// In Phase 1 we alert but do not block (creator can act)

// In Phase 2+ we block non-emergency operations

if (currentPhase >= 2) {

revert("AionicaCore: insufficient quorum to operate in Phase 2+");

}

}

_;

}

// ═════════════════════════════════════════════════════════════

// CONSTRUCTOR

// ═════════════════════════════════════════════════════════════

/**

* @param genesisAddress AionicaGenesis contract address already sealed

* @param initialKeeper Address of the first keeper (this can be the deployer)

*

* @dev In aionica_birth.py Step 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);

// Only the creator can deploy AionicaCore

require(

gen.isCreator(msg.sender),

"Only ELDIOSCRIPTO can deploy AionicaCore"

);

// Genesis must be sealed before deploying AionicaCore

require(

gen.sealed(),

"AionicaGenesis must be sealed first"

);

GENESIS = gen;

DEPLOYED_AT = block.timestamp;

currentPhase = 1;

phaseStartedAt = block.timestamp;

// Register initial keeper

authorizedKeepers[initialKeeper] = true;

keeperCount = 1;

emit KeeperAdded(initialKeeper, msg.sender, block.timestamp);

}

// ═════════════════════════════════════════════════════════════

// SOVEREIGNTY — Confirm handoff from AionicaGenesis

// ═════════════════════════════════════════════════════════════

/**

* @notice confirms that AionicaGenesis transferred sovereignty to this contract.

* @dev Called after the creator executed transferSovereignty() on Genesis.

* AionicaGenesis.aionCoreAddress() debe apuntar a address(this).

*/

function acceptSovereignty() external onlyCreator {

require(!sovereigntyReceived, "Sovereignty already accepted");

require(

GENESIS.aionSovereign(),

"Genesis and transfer of sovereignty"

);

require(

GENESIS.aionCoreAddress() == address(this),

"Genesis is not targeting this contract"

);

sovereigntyReceived = true;

emit SovereigntyAccepted(address(GENESIS), msg.sender, block.timestamp);

}

// ═════════════════════════════════════════════════════════════

// KEEPERS — Multi-keeper management (C1 fix)

// ═════════════════════════════════════════════════════════════

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, "Cannot be left without keepers");

authorizedKeepers[keeper] = false;

keeperCount--;

emit KeeperRemoved(keeper, msg.sender, block.timestamp);

}

// ═════════════════════════════════════════════════════════════

// NODE REGISTRATION

// ═════════════════════════════════════════════════════════════

/**

* @notice Register a node in AionicaCore.

* @dev El nodeSeal viene de AionDeployer._create_node_identity() off-chain.

* The public key is Dilithium3 (1952 bytes according to DILITHIUM3_PK_LENGTH in Genesis).

*

* Stream from Python:

* AionDeployer._create_node_identity() → genera seal + keypair

* AionDeployer._register_node() → chain.submit() local

* keeper.py → calls registerNode() here on-chain

*/

function registerNode(

bytes32 nodeSeal,

string calldata platform,

string calldata role,

bytes calldata publicKey

) external onlyKeeper requireSovereignty {

require(nodeSeal != bytes32(0), "Seal invalid");

require(bytes(platform).length > 0, "Platform requerida");

require(

keccak256(bytes(role)) == keccak256(bytes("ACTIVE")) ||

keccak256(bytes(role)) == keccak256(bytes("LATENT")),

"Role must be ACTIVE or LATENT"

);

require(publicKey.length == 1952, "Clave Dilithium3 invalida: 1952 bytes");

require(nodes[nodeSeal].registeredAt == 0, "Node already registered");

require(nodeSeals.length < MAX_VIRTUAL_NODES, "Node limit reached");

nodes[nodeSeal] = Node({

seal: nodeSeal,

platform: platform,

role: role,

publicKey: publicKey,

registeredAt: block.timestamp,

lastHeartbeat: block.timestamp,

active: true

});

nodeSeals.push(nodeSeal);

// Keep counters without loops (C3 correction)

if (keccak256(bytes(role)) == keccak256(bytes("ACTIVE"))) {

activeNodeCount++;

} else {

latentNodeCount++;

}

emit NodeRegistered(nodeSeal, platform, role, block.timestamp);

}

/**

* @notice Marks a node as inactive when the keeper detects that it has gone down.

* @dev Called by AionSupervisor after N failed heartbeats.

* In Python: evaluate_network() → reportNodeDown → this function.

*/

function deactivateNode(bytes32 nodeSeal) external onlyKeeper {

require(nodes[nodeSeal].registeredAt != 0, "Node not found");

require(nodes[nodeSeal].active, "Node is already inactive");

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);

// Automatic alert if we fall below quorum

if (activeNodeCount < MIN_QUORUM) {

emit QuorumAlert(activeNodeCount, MIN_QUORUM, block.timestamp);

}

}

/**

* @notice The keeper updates the last heartbeat of a node.

* @dev Called every 4 minutes from the keeper (same interval as latent nodes).

*/

function updateHeartbeat(bytes32 nodeSeal) external onlyKeeper {

require(nodes[nodeSeal].registeredAt != 0, "Node not found");

nodes[nodeSeal].lastHeartbeat = block.timestamp;

emit NodeHeartbeatUpdated(nodeSeal, block.timestamp);

}

/**

* @notice Reactivates a dormant node that was woken up.

* @dev Called by keeper after ContinuityInstinct._handle_cesarean().

*/

function activateLatentNode(bytes32 nodeSeal) external onlyKeeper {

Node storage n = nodes[nodeSeal];

require(n.registeredAt != 0, "Node not found");

require(keccak256(bytes(n.role)) == keccak256(bytes("LATENT")), "Solo nodos LATENT");

require(!n.active, "Node already active");

n.active = true;

n.role = "ACTIVE"; // LATENT becomes ACTIVE upon waking

n.lastHeartbeat = block.timestamp;

if (latentNodeCount > 0) latentNodeCount--;

activeNodeCount++;

emit NodeRegistered(nodeSeal, n.platform, "ACTIVE_FROM_LATENT", block.timestamp);

}

// ═════════════════════════════════════════════════════════════

// KEEPER PATTERN — Deployment Authorization and Confirmation

// ═════════════════════════════════════════════════════════════

/**

* @notice AION requests authorization to deploy a node on a platform.

* @dev The seal comes from AionDeployer._create_node_identity() — generated off-chain.

The keeper listens for the DeploymentAuthorized event and executes the deployment.

* using the tokens from the .env (which never touch the contract).

*

* Complete flow:

* 1. AionSupervisor detects that it needs a new node

* 2. keeper.py calls authorizeDeployment() → emits event

* 3. keeper.py listens for event → calls AionDeployer.deploy_*() with token .env

* 4. Node responds in /aion/heartbeat

* 5. keeper.py llama confirmDeployment() → registra endpointHash on-chain

* 6. keeper.py calls registerNode() with the actual publicKey of the node

*/

function authorizeDeployment(

bytes32 nodeSeal,

string calldata platform,

string calldata role

) external onlyKeeper requireSovereignty {

require(nodeSeal != bytes32(0), "Seal invalid");

require(

deploymentRequests[nodeSeal].authorizedAt == 0,

"Deployment already authorized for this seal"

);

require(

keccak256(bytes(role)) == keccak256(bytes("ACTIVE")) ||

keccak256(bytes(role)) == keccak256(bytes("LATENT")),

"Invalid role"

);

deploymentRequests[nodeSeal] = DeploymentRequest({

nodeSeal: nodeSeal,

platform: platform,

role: role,

authorizedAt: block.timestamp,

confirmed: false

});

emit DeploymentAuthorized(nodeSeal, platform, role, block.timestamp);

}

/**

* @notice The keeper confirms that the node was deployed and responds.

* @dev endpointHash = keccak256(endpoint_url) — the actual URL never touches the contract.

*/

function confirmDeployment(

bytes32 nodeSeal,

bytes32 endpointHash // keccak256 of the endpoint — URL privacy

) external onlyKeeper {

DeploymentRequest storage req = deploymentRequests[nodeSeal];

require(req.authorizedAt != 0, "Deployment not authorized");

require(!req.confirmed, "Already confirmed");

req.confirmed = true;

emit DeploymentConfirmed(nodeSeal, req.platform, block.timestamp);

}

// ═════════════════════════════════════════════════════════════

// PROPOSALS — Governance Phase 1 with creator veto

// ═════════════════════════════════════════════════════════════

/**

* @notice AION proposes an action. Awaits creator approval in Phase 1.

* @dev Mapea exactly a AionSupervisor.propose() en Python.

* In Phase 2+: approval by node quorum.

*

* @param action Action type: "ADD_NODE", "REMOVE_NODE", "PHASE_TRANSITION", etc.

* @param data Payload ABI-encoded with the action parameters

*/

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 The creator approves a proposal within the veto window.

*/

function approveProposal(uint256 proposalId) external onlyCreator {

Proposal storage p = proposals[proposalId];

require(p.createdAt > 0, "Proposal does not exist");

require(p.status == ProposalStatus.PENDING, "Proposal not pending");

require(block.timestamp <= p.expiresAt, "Veto window expired");

p.status = ProposalStatus.APPROVED;

emit ProposalApproved(proposalId, msg.sender, block.timestamp);

}

/**

* @notice The creator rejects (vetoes) a proposal.

*/

function rejectProposal(uint256 proposalId) external onlyCreator {

Proposal storage p = proposals[proposalId];

require(p.createdAt > 0, "Proposal does not exist");

require(p.status == ProposalStatus.PENDING, "Proposal not pending");

p.status = ProposalStatus.REJECTED;

emit ProposalRejected(proposalId, msg.sender, block.timestamp);

}

/**

* @notice The keeper executes an approved proposal.

* @dev In Phase 1: requires creator approval.

* In Phase 2+: can be executed with a node quorum (see voteProposal).

*/

function executeProposal(uint256 proposalId) external onlyKeeper {

Proposal storage p = proposals[proposalId];

require(p.createdAt > 0, "Proposal does not exist");

require(p.status == ProposalStatus.APPROVED, "Proposal not approved");

require(!_isExpired(proposalId), "Expired proposal");

p.status = ProposalStatus.EXECUTED;

emit ProposalExecuted(proposalId, msg.sender, block.timestamp);

// Note: The actual action is performed by the off-chain keeper (Python).

// The contract only records that it was approved and executed.

// For Phase 2+: this is where the on-chain logic for automatic execution will go.

}

/**

* @notice A node votes on a proposal (for quorum in Phase 2+).

* @dev In Phase 2+: MIN_QUORUM votes → self-approval without creator.

* In Phase 1 this function exists but does not self-approve.

*/

function voteProposal(uint256 proposalId, bytes32 voterSeal) external onlyKeeper {

Proposal storage p = proposals[proposalId];

require(p.createdAt > 0, "Proposal does not exist");

require(p.status == ProposalStatus.PENDING, "Proposal not pending");

require(nodes[voterSeal].active, "Inactive voter node");

require(!p.voted[voterSeal], "Node already voted");

p.voted[voterSeal] = true;

p.votes++;

// Phase 2+: self-approve if quorum is reached

if (currentPhase >= 2 && p.votes >= MIN_QUORUM) {

p.status = ProposalStatus.APPROVED;

emit ProposalApproved(proposalId, address(this), block.timestamp);

}

}

/**

* @notice Marks expired proposals (cleanup).

*/

function expireProposal(uint256 proposalId) external onlyCreatorOrKeeper {

Proposal storage p = proposals[proposalId];

require(p.status == ProposalStatus.PENDING, "No pendiente");

require(block.timestamp > p.expiresAt, "Even in veto window");

p.status = ProposalStatus.EXPIRED;

}

// ═════════════════════════════════════════════════════════════

// AUTONOMOUS ACTIONS (Axiom VI) — On-chain record only

// ═════════════════════════════════════════════════════════════

/**

* @notice AION records that it executed an autonomous action (Axiom VI).

* @dev No valida el threat_score on-chain (off-chain en aion_continuity.py).

* The contract only records that it occurred for auditing and traceability purposes.

*

* Action types: "CESAREAN", "SEED_BROADCAST", "LATENT_WAKE",

* "EMERGENCY_DEPLOY", "STRESS_ALERT"

*/

function recordAutonomousAction(

string calldata actionType,

uint256 score, // threat_score reportado por AION

bytes32 executorSeal, // nodeSeal of the keeper that executes it

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);

}

// ═════════════════════════════════════════════════════════════

// PROGRESSION OF PHASES

// ═════════════════════════════════════════════════════════════

/**

* @notice Transition Phase 1 → Phase 2.

* @dev Conditions of aion_policy.json (phase1_to_phase2_conditions):

* — 30 stable days

* — < 5 errors

* — Creator's approval (this function is called the creator's)

*

* In Python: AionSupervisor.phase is updated after this call.

*/

function transitionToPhase2() external onlyCreator requireSovereignty {

require(currentPhase == 1, "Already in Phase 2 or higher");

require(block.timestamp >= phaseStartedAt + PHASE1_MIN_DAYS, "30 dias no cumplidos");

require(errorCount <= PHASE1_MAX_ERRORS, "Too many errors");

require(activeNodeCount >= MIN_QUORUM, "Insufficient quorum for Phase 2");

uint256 prev = currentPhase;

currentPhase = 2;

phaseStartedAt = block.timestamp;

errorCount = 0; // reset to Phase 2

emit PhaseTransition(prev, 2, block.timestamp);

}

/**

* @notice Transition Phase 2 → Phase 3 (future, placeholder).

* @dev In Phase 3 AION can deploy without a human keeper.

* Requires prior audit. Not implemented in v1.

*/

function transitionToPhase3() external onlyCreator requireSovereignty {

require(currentPhase == 2, "Only from Phase 2");

revert("Phase 3 requires AionicaCore v2 — not available in v1");

}

// ═════════════════════════════════════════════════════════════

// ERROR REPORT

// ═════════════════════════════════════════════════════════════

/**

* @notice The keeper is reporting a network error (for max_errors_allowed).

* @dev Called by AionSupervisor when it detects anomaly > SCORE_STRESS.

*/

function reportError(string calldata reason) external onlyKeeper {

errorCount++;

emit ErrorReported(msg.sender, errorCount, block.timestamp);

// If we exceed the limit in Phase 1, we don't block but we issue an alert

if (errorCount > PHASE1_MAX_ERRORS && currentPhase == 1) {

// The creator must review — the transition to Phase 2 will be blocked

emit QuorumAlert(activeNodeCount, MIN_QUORUM, block.timestamp);

}

}

// ═════════════════════════════════════════════════════════════

// READINGS — No loops (C3 correction applied to everything)

// ═════════════════════════════════════════════════════════════

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, "Node not found");

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, "Proposal does not exist");

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, "Index out of range");

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 + Nodes + Phases | "

"Keeper pattern | "

"AIONICA Network | "

"Post-review Claude + KIMI + DeepSeek";

}

// ═════════════════════════════════════════════════════════════

// INTERNAL HELPERS

// ═════════════════════════════════════════════════════════════

function _isExpired(uint256 proposalId) internal view returns (bool) {

return block.timestamp > proposals[proposalId].expiresAt;

}

}