feat(experimental)!: multi-master one ledger (Option B)
Remove the last layer of soft centralization: a lone `--master` on empty storage could still implicitly genesis a separate Autopass and split the network on the same TOPIC_SEED. Genesis is now explicit (`--master --genesis`); secondary masters pair via invite into the same dnsPass as joiners, with a shared network manifest, split-brain diagnostics, and writer-aware quorum. - Genesis vs secondary master boot paths; auto-adopt manifest on upgrade - Masters without dnsPass accept invites; masters with pass ignore them - NETWORK_MANIFEST_FILE, P2NS_GENESIS, MASTER_LOAD_DOMAINS, MASTER_INVITE_ONLY - core.status networkId; admin diagnostics; docs and multi-master tests BREAKING: operators must run one genesis per network; additional masters use `node p2ns.js --master` (not `--genesis`) on empty storage.
This commit is contained in:
@@ -947,12 +947,21 @@ function displayInviteDiagnostics(diagnostics) {
|
||||
const contentDiv = document.createElement('div');
|
||||
contentDiv.className = 'space-y-2';
|
||||
|
||||
const networkIdShort = diagnostics.networkId
|
||||
? `${String(diagnostics.networkId).slice(0, 16)}…`
|
||||
: 'n/a';
|
||||
const statusItems = [
|
||||
{ label: 'Node', value: diagnostics.nodeType, color: 'text-white' },
|
||||
{ label: 'Network ID', value: networkIdShort, color: diagnostics.networkId ? 'text-indigo-300' : 'text-gray-400' },
|
||||
{ label: 'DNS Pass', value: diagnostics.dnsPassInitialized ? 'Ready' : 'Missing', color: diagnostics.dnsPassInitialized ? 'text-green-400' : 'text-red-400' },
|
||||
{ label: 'Peers', value: String(totalPeers), color: 'text-white' },
|
||||
{ label: 'Control', value: protocol.control || 'p2ns.core-request-rpc', color: 'text-indigo-300' }
|
||||
];
|
||||
if (diagnostics.masterPendingPass) {
|
||||
statusItems.splice(1, 0, { label: 'Role', value: 'Secondary master (awaiting invite)', color: 'text-yellow-400' });
|
||||
} else if (diagnostics.isGenesis) {
|
||||
statusItems.splice(1, 0, { label: 'Role', value: 'Genesis master', color: 'text-green-400' });
|
||||
}
|
||||
|
||||
contentDiv.innerHTML += `
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2 text-sm">
|
||||
@@ -1010,6 +1019,12 @@ function displayInviteDiagnostics(diagnostics) {
|
||||
if (inFlight > 0) {
|
||||
alerts.push(`<div class="text-yellow-300 text-xs">⚙️ ${inFlight} in-flight invite.request handler(s)</div>`);
|
||||
}
|
||||
if ((diagnostics.splitBrainWarnings || []).length > 0) {
|
||||
alerts.push(`<div class="text-red-300 text-xs">🚨 ${diagnostics.splitBrainWarnings.length} split-network warning(s) — peer networkId mismatch</div>`);
|
||||
}
|
||||
if (diagnostics.masterPendingPass) {
|
||||
alerts.push(`<div class="text-yellow-300 text-xs">⏳ Secondary master: waiting for invite to join the network ledger</div>`);
|
||||
}
|
||||
if ((diagnostics.consecutiveInviteFailures || 0) > 0) {
|
||||
const n = diagnostics.consecutiveInviteFailures;
|
||||
const severity = n >= 3 ? 'text-red-300' : 'text-orange-300';
|
||||
|
||||
@@ -274,6 +274,9 @@ function renderCoreStats(core) {
|
||||
if (protocolEl) {
|
||||
const parts = [
|
||||
core.nodeType ? `node=${core.nodeType}` : null,
|
||||
core.networkId ? `network=${String(core.networkId).slice(0, 12)}…` : null,
|
||||
core.isGenesis ? 'genesis' : null,
|
||||
core.masterPendingPass ? 'pending-pass' : null,
|
||||
protocol.control ? `control=${protocol.control}` : null,
|
||||
protocol.inviteWire ? `wire=${protocol.inviteWire}` : null,
|
||||
core.schemaVersion ? `schema=v${core.schemaVersion}` : null
|
||||
@@ -289,6 +292,15 @@ function renderCoreStats(core) {
|
||||
const peerEntries = core.peers ? Object.entries(core.peers) : [];
|
||||
let html = '';
|
||||
|
||||
if ((core.splitBrainWarnings || []).length > 0) {
|
||||
html += `
|
||||
<div class="theme-glass rounded-lg p-3 text-sm border border-red-500/40">
|
||||
<span class="text-red-400 font-semibold">Split network:</span>
|
||||
<span class="theme-text-secondary"> ${core.splitBrainWarnings.length} peer(s) report a different networkId</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (masterQueue > 0 || inFlight > 0) {
|
||||
html += `
|
||||
<div class="theme-glass rounded-lg p-3 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user