#!/usr/bin/env node
'use strict';
/*
 * ═══════════════════════════════════════════════════════════════════════════
 *  SKYCLOUD — ענן פרטי וסינכרון, קובץ אחד, ללא שרת, ללא חשבון, ללא UI
 * ═══════════════════════════════════════════════════════════════════════════
 *
 *  איך משתמשים:
 *    1. זורקים את הקובץ הזה (cloud.js) לתיקייה של כל פרויקט.
 *    2. מריצים:  node cloud.js
 *       בהרצה הראשונה נוצר SYNC_ID ייחודי ונכתב אוטומטית לתוך הקובץ (למטה).
 *    3. במחשב/מקום אחר: שמים את אותו cloud.js עם אותו SYNC_ID ומריצים.
 *       הכול מסתנכרן — גם אם המקור הראשון כבוי לגמרי.
 *
 *  הטמעה בתוך קוד (ללא UI):
 *      const cloud = require('./cloud.js');
 *      await cloud.ready();                    // מחובר לרשת ומסונכרן
 *      await cloud.set('score', 123);          // שמירת נתון בענן
 *      const v = cloud.get('score');           // קריאת נתון
 *      cloud.onChange((key, value) => { ... })  // עדכון חי מכל מקום בעולם
 *
 *  מצבים מהטרמינל:
 *      node cloud.js            סינכרון חי ורציף (משאיר דולק כל עוד רוצים)
 *      node cloud.js --once     סינכרון חד-פעמי ויציאה (משוך+דחוף הכול)
 *      node cloud.js --deploy   בנייה (BUILD_COMMAND) + פרסום האתר והסינכרון
 *      node cloud.js --status   הצגת מצב, ID והכתובת הגלובלית
 *      node cloud.js --health   ניטור בריאות הרשת החי (Θ: latency/phi/score)
 *      node cloud.js --mind     דופק המנוע החי (Λ: חיוניות/אנטרופיה/אודיט/החלטה)
 *      node cloud.js --formula "clamp(vitality*100,0,100)"   הרצת נוסחה על הטלמטריה
 *      node cloud.js --solve '{"op":"factorize","args":{"n":97}}'   חישוב טהור (Ξ)
 *      node cloud.js --dag '{"nodes":{...},"output":"..."}'    צנרת-חישוב (Ξ/DAG)
 *      node cloud.js --serve-compute    הפעלת פותר-עצמאי לרשת-החישוב (Ξ)
 *      node cloud.js --name <שם> <יעד>  שם קריא/משתנה/נודד → יעד (Roam)
 *      node cloud.js --resolve <שם>     פתירת שם נודד ליעד העדכני (Roam)
 *      node cloud.js --publish-site <שם> '{dag}'   פרסום אתר-חי מחושב (שם→DAG)
 *      node cloud.js --resolve-site <שם> [inputs]  פתירת אתר-חי מחושב → תוכן דינמי
 *      node cloud.js --import x.zip      ייבוא מערכת שלמה מ-ZIP (פריקה+זיהוי)
 *      node cloud.js --new-id   יצירת SYNC_ID חדש (ענן חדש ריק)
 *      node cloud.js --shares 5 3        פיצול ה-ID ל-5 רסיסי-סוד (כל 3 משחזרים)
 *      node cloud.js --recover <רסיסים>  שחזור ה-ID מרסיסים
 *      node cloud.js --allow-mining      אישור מפורש לתרומת-עיבוד (אם MINING.enabled)
 *      node cloud.js --policy-kit [site] ערכת מדיניות + מעטפת Policy CDN
 *      node cloud.js --full-node         SkyDB Ω + site/server + bridge (Node מלא)
 *
 *  שרת אפליקציה חי על הכתובת הגלובלית (Ω) — לא סטטי:
 *      const cloud = require('./cloud.js'); await cloud.ready();
 *      cloud.route('GET', '/api/hello', async (req) => ({ msg: 'hi' }));
 *      // או גשר לשרת אמיתי (Next.js/Express): cloud.bridge('http://127.0.0.1:3000')
 *      // בדפדפן: כל fetch('/api/…') מנותב אוטומטית לעובד-שרת חי ברשת.
 *
 *  איך זה עובד בלי שרת ובלי שמשהו שלך דולק:
 *      המידע מוצפן (AES-256-GCM) ומשוגר לעשרות ממסרים ציבוריים פתוחים של
 *      רשת Nostr — רשת גלובלית מבוזרת שהיא חלק מהאינטרנט. המידע משוכפל
 *      בכמה מקומות בעולם במקביל, ונשאר שמור שם גם כשהכול אצלך כבוי.
 *      מי שמחזיק את אותו SYNC_ID (ורק הוא) יכול למשוך ולפענח את המידע,
 *      מכל מקום בעולם. תמיד מנצח העדכון האחרון (last-write-wins).
 *
 *  הכתובת הגלובלית:
 *      הכלי מפרסם עמוד סטטוס חתום לרשת, שזמין תמיד בכתובת קבועה דרך
 *      שערי רשת ציבוריים (מודפסת בהפעלה) — גם כשכל המכשירים שלך כבויים.
 * ═══════════════════════════════════════════════════════════════════════════
 */

// ─────────────────────────── הגדרות (אפשר לערוך) ───────────────────────────

// ה-ID של הענן. 'AUTO' = ייווצר לבד בהרצה הראשונה וייכתב לכאן אוטומטית.
// כדי לחבר מקום נוסף לאותו ענן — מעתיקים לשם את הקובץ עם אותו SYNC_ID.
const SYNC_ID = 'SC-372514B0C4747E61ED1115A8D5A9695C14962C59';

// אילו קבצים/תיקיות לא לסנכרן
const IGNORE = [
  'node_modules', '.git', '.svn', '.hg', '__pycache__', '.venv', 'venv',
  '.cloud-state.json', '.cloud-tmp', '.cloud-skydb', 'cloud.js', '.DS_Store', 'Thumbs.db',
  'dist', 'build', '.next', '.cache',
];

// ממסרים ציבוריים חופשיים של הרשת (אפשר להוסיף/להחליף)
const RELAYS = [
  'wss://relay.damus.io',
  'wss://nos.lol',
  'wss://relay.primal.net',
  'wss://relay.nostr.band',
  'wss://nostr.mom',
  'wss://offchain.pub',
  'wss://nostr.oxtr.dev',
  'wss://relay.nostr.bg',
  'wss://nostr.wine',
  'wss://relay.snort.social',
  'wss://nostr.bitcoiner.social',
  'wss://relay.nostrplebs.com',
];

// ממסרי-מילואים ("נתיבים חלופיים"): נכנסים לשימוש רק כשמנהל-המסלולים (Θ)
// מזהה שהנתיב הפעיל נחסם/מתדרדר — החלפת-מסלול דינמית (Auto-Proxy / hopping).
const RELAYS_FALLBACK = [
  'wss://relay.nostr.net',
  'wss://nostr.land',
  'wss://relay.mostr.pub',
  'wss://nostr21.com',
  'wss://relay.nostr.wirednet.jp',
  'wss://nostr.rocks',
  'wss://relay.current.fyi',
  'wss://eden.nostr.land',
];

// אתר ציבורי חי (nsite / NIP-5A): true = הפרויקט מקבל כתובת אתר אמיתית ברשת
// המבוזרת, שמציגה את התוכן ומתעדכנת בכל סינכרון — גם כשכל המכשירים כבויים.
// שים לב: במצב זה קבצי הפרויקט גלויים לצפייה בכתובת (לא מוצפנים).
// false = ענן מוצפן בלבד, בלי אתר ציבורי.
const PUBLIC_WEBSITE = true;

// ─────────── הגדרות האתר (כמו firebase.json / vercel.json — הכול כאן) ───────────

// שם האתר: 'AUTO' = שם תיקיית הפרויקט. שם בעברית מתורגם לאנגלית אוטומטית.
const SITE_NAME = 'AUTO';

// מאיזו תיקייה מגישים את האתר: 'AUTO' = זיהוי אוטומטי — אם קיימת תיקיית
// dist / build / public / www / site / web / docs עם index.html היא תוגש
// כשורש האתר; אחרת כל הפרויקט. אפשר לקבוע ידנית, למשל: 'dist'.
const SITE_DIR = 'site';

// פקודת בנייה שרצה בטרמינל לפני פרסום האתר (כמו ב-Vercel/Firebase).
// למשל: 'npm run build'. ריק = בלי שלב בנייה.
const BUILD_COMMAND = '';

// דומיין משלך (אופציונלי): למשל 'www.myproject.io'. דומיין אמיתי חייב
// להירכש אצל רשם דומיינים (עלות שנתית) — אין דרך חוקית לקבל .io בחינם,
// אצל אף שירות בעולם. אחרי רכישה: מפנים רשומת CNAME מהדומיין אל כתובת
// ה-npub של האתר (מודפסת ב---status) והאתר המבוזר מוגש מהדומיין שלך.
const CUSTOM_DOMAIN = '';

// שרתי קבצים ציבוריים מבוזרים (Blossom) שמאחסנים את קבצי האתר.
// הסדר חשוב: שערי האתר פונים אליהם לפי הסדר, והראשונים מגישים
// Content-Type תקין (CSS/JS מוצגים נכון בדפדפן).
const BLOSSOM_SERVERS = [
  'https://cdn.nostrcheck.me',
  'https://nostr.download',
  'https://blossom.primal.net',
  'https://blossom.band',
];

// שערים ציבוריים שמגישים את האתר מהרשת המבוזרת (אותו אתר בכולם).
// מסודרים לפי מהירות/אמינות שנמדדו בפועל. אם רשת חוסמת אחד — נסה את הבא.
const NSITE_GATEWAYS = ['nosto.re', 'nwb.tf', 'nsite.cloud', 'nsite.run', 'nsite.lol'];

// ─────────── שרת אפליקציה חי על הכתובת הגלובלית (SkyLattice-Ω) ───────────
// הכתובת הגלובלית מגישה מעטפת סטטית (SPA) + מנוע הדפדפן. כל קריאת fetch
// לנתיב שמתחיל ב-API_PREFIX מנותבת אוטומטית דרך הרשת המבוזרת אל "צד שרת"
// חי — כל מכשיר Node שמריץ את cloud.js ורשם route()/bridge() הוא עובד-שרת.
// כך מערכת מלאה (כולל Next.js/סוכני AI) חיה בכתובת כל עוד עובד אחד פעיל
// כלשהו בעולם; כשכולם כבויים נשארת המעטפת הסטטית + הנתונים במטמון (כמו CDN).
const API_PREFIX = '/api';         // כל fetch לנתיב הזה מנותב לצד-השרת המבוזר

// גשר לשרת מקומי אמיתי (Next.js/Express/כל שפה): אם מוגדר, בקשות ה-API
// מהכתובת הגלובלית מגושרות אל השרת הזה שרץ אצלך (למשל 'http://127.0.0.1:3000').
// כך הכתובת הגלובלית מריצה שרת Node *מלא* — לא סטטי — דרך הרשת המבוזרת.
const LOCAL_BRIDGE = '';

// SkyLattice-ΔB / SkyDB + Node מלא: כש-true (או CLI --full-node) — מפעיל Ω /api/db/*
// + שרת site/server.js על פורט FULL_NODE_PORT ומגשר אליו. הרשת ≠ מיליוני כתיבות;
// הנתיב החם הוא WAL מקומי; ממסרים מקבלים checkpoints בלבד.
const FULL_NODE = false;
const FULL_NODE_PORT = 8787;
const SKYDB_AUTO = true;           // enableApi אוטומטי אחרי ready (גם בלי --full-node)
const SKYDB_ENGINE = 'sqlite';     // sqlite | memory | postgres | redis
const SKYDB_SHARDS = 8;

// כרייה/תרומת-עיבוד: כבוי כברירת מחדל. הפעלה דורשת הסכמה מפורשת בזמן ריצה
// (--allow-mining) — לעולם לא רץ בשקט אצל מי שלא אישר. פרטים: ראה MiningConfig.
const MINING = {
  enabled: false,        // true = מציע תרומת-עיבוד (עדיין דורש אישור מפורש בהרצה)
  wallet: '',            // כתובת הארנק שאליה זוכפות התרומות
  coin: '',              // סמל המטבע, למשל 'XMR'
  network: '',           // הרשת/פּוּל, למשל 'monero:pool.example:3333'
  maxCpuPercent: 15,     // תקרת עומס מעבד (%) — לא ממוטט מכשירים
};

const CHUNK_SIZE = 16 * 1024;      // גודל חלק לקובץ (16KB לפני הצפנה, בטוח לכל ממסר)
const API_INLINE_MAX = 48 * 1024;  // תגובת API גדולה מזה עוברת דרך Blossom ולא inline
const KIND_DATA = 30078;           // אירוע נתונים ניתן-להחלפה (NIP-78)
const KIND_SITE = 30023;           // עמוד הסטטוס הציבורי (NIP-23)
const KIND_NSITE = 15128;          // מניפסט אתר סטטי (NIP-5A)
const KIND_BLOSSOM_LIST = 10063;   // רשימת שרתי הקבצים של המשתמש (BUD-03)
const LIVE_POLL_MS = 45_000;       // בדיקת עדכונים יזומה כגיבוי למנוי החי
const HEAL_SWEEP_MS = 300_000;     // Θ: מחזור ריפוי-עצמי (רענון שכפול + אימות שלמות)
const DEBOUNCE_MS = 900;           // השהיה אחרי שינוי קובץ לפני שיגור

// ────────────────────────────── בסיס ──────────────────────────────

const crypto = require('node:crypto');
const fs = require('node:fs');
const fsp = require('node:fs/promises');
const path = require('node:path');
const { exec } = require('node:child_process');

const ROOT = __dirname;
const STATE_FILE = path.join(ROOT, '.cloud-state.json');

// הכלי משתמש ב-WebSocket וב-fetch המובנים של Node — נדרש Node 21 ומעלה
if (typeof WebSocket === 'undefined' || typeof fetch === 'undefined') {
  const msg = '[cloud] נדרש Node.js 21 ומעלה (WebSocket/fetch מובנים). הגרסה הנוכחית: ' + process.version;
  if (require.main === module) { console.error(msg); process.exit(1); }
  throw new Error(msg);
}

// ─────────── שם הפרויקט: זיהוי עברית/אנגלית ותרגום לאנגלית תקינה ───────────

const HEB2EN = {
  'א': 'a', 'ב': 'b', 'ג': 'g', 'ד': 'd', 'ה': 'h', 'ו': 'v', 'ז': 'z',
  'ח': 'ch', 'ט': 't', 'י': 'y', 'כ': 'k', 'ך': 'k', 'ל': 'l', 'מ': 'm',
  'ם': 'm', 'נ': 'n', 'ן': 'n', 'ס': 's', 'ע': 'a', 'פ': 'p', 'ף': 'f',
  'צ': 'ts', 'ץ': 'ts', 'ק': 'k', 'ר': 'r', 'ש': 'sh', 'ת': 't',
};

/** הופך כל שם (גם בעברית) לשם אתר אנגלי נקי, למשל "האתר שלי" -> "hatr-shly" */
function slugify(name) {
  const translit = [...name].map((c) => HEB2EN[c] ?? c).join('');
  const slug = translit
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 40);
  return slug || 'my-project';
}

const PROJECT_NAME = path.basename(ROOT);
const SITE_SLUG = SITE_NAME === 'AUTO' ? slugify(PROJECT_NAME) : slugify(SITE_NAME);

/** תיקיית האתר בפועל: מוגדרת ידנית, מזוהה אוטומטית, או שורש הפרויקט */
function resolveSiteDir() {
  if (SITE_DIR !== 'AUTO') {
    if (fs.existsSync(path.join(ROOT, SITE_DIR, 'index.html'))) return SITE_DIR;
    log(`אזהרה: SITE_DIR='${SITE_DIR}' לא מכיל index.html — האתר יוגש משורש הפרויקט.`);
    return null;
  }
  for (const dir of ['dist', 'build', 'public', 'www', 'site', 'web', 'docs']) {
    if (fs.existsSync(path.join(ROOT, dir, 'index.html'))) return dir;
  }
  return null;
}

/**
 * זיהוי קובץ הפעלה (.bat / .cmd) שפותח דף HTML בדפדפן.
 * אם בפרויקט יש למשל run.bat עם "start app.html" — אותו דף הופך אוטומטית
 * לדף הבית של הכתובת הגלובלית, בדיוק כמו שהוא נפתח מקומית.
 */
function resolveBatEntry() {
  let names = [];
  try { names = fs.readdirSync(ROOT); } catch {}
  for (const name of names.filter((n) => /\.(bat|cmd)$/i.test(n))) {
    let text = '';
    try { text = fs.readFileSync(path.join(ROOT, name), 'utf8'); } catch { continue; }
    for (const rawLine of text.split(/\r?\n/)) {
      const line = rawLine.trim();
      if (!line || /^(rem\b|::)/i.test(line)) continue;
      // תומך גם בנתיבים עם רווחים בתוך מרכאות: start "" "my page.html"
      const m = line.match(/"([^"]+\.html?)"|'([^']+\.html?)'|(\S+\.html?)\b/i);
      if (!m) continue;
      const raw = m[1] ?? m[2] ?? m[3];
      if (/^https?:\/\//i.test(raw)) continue; // כתובת אינטרנט/localhost — לא קובץ מקומי
      const rel = raw.replace(/\\/g, '/').replace(/^\.\//, '');
      if (fs.existsSync(path.join(ROOT, rel))) return { bat: name, html: rel };
    }
  }
  return null;
}
const sha256 = (...bufs) => {
  const h = crypto.createHash('sha256');
  for (const b of bufs) h.update(b);
  return h.digest();
};
const hex = (b) => Buffer.from(b).toString('hex');
const unhex = (s) => Buffer.from(s, 'hex');
const now = () => Math.floor(Date.now() / 1000);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const log = (...a) => console.log('[cloud]', ...a);

// ─────────── Θ: שעון לוגי היברידי (Hybrid Logical Clock) ───────────
// זמן פיזי + מונה מונוטוני. מבטיח סדר אירועים עקבי (last-write-wins) גם
// כששעוני המכשירים סוטים זה מזה — הערך תמיד עולה, ותמיד בר-השוואה מול
// חותמות Date.now() רגילות (כולל גרסאות ישנות של המנוע). זהו תיקון-עצמי
// מתמטי לסטיית שעונים: אין צורך בשרת זמן מרכזי.
let _hlcLast = 0;
function hlcNow() {
  const phys = Date.now();
  _hlcLast = phys > _hlcLast ? phys : _hlcLast + 1;
  return _hlcLast;
}
/** קליטת חותמת מרוחקת — מקדם את השעון המקומי מעבר לכל זמן שנצפה */
function hlcObserve(remoteTs) {
  if (typeof remoteTs === 'number' && remoteTs > _hlcLast) _hlcLast = remoteTs;
}

// ─────────── פורק ZIP טהור (ללא תלות) — לייבוא מערכת שלמה ───────────
// קורא את ספריית המרכז (Central Directory) של ארכיון PKZIP ומחלץ כל קובץ:
// שיטת אחסון 0 (stored) או 8 (deflate → zlib.inflateRawSync). כך אפשר לזרוק
// zip של מערכת שלמה, המנוע יפרוק אותה, יזהה בנייה/הפעלה ויגיש לכתובת.
const zlib = require('node:zlib');
function unzip(buf) {
  const files = [];
  // איתור End Of Central Directory (EOCD): חתימה 0x06054b50 מהסוף
  let eocd = -1;
  for (let i = buf.length - 22; i >= 0 && i >= buf.length - 22 - 65536; i--) {
    if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
  }
  if (eocd < 0) throw new Error('ZIP לא תקין: לא נמצא EOCD');
  const total = buf.readUInt16LE(eocd + 10);
  let ptr = buf.readUInt32LE(eocd + 16); // היסט ספריית המרכז
  for (let n = 0; n < total; n++) {
    if (buf.readUInt32LE(ptr) !== 0x02014b50) break;
    const method = buf.readUInt16LE(ptr + 10);
    const compSize = buf.readUInt32LE(ptr + 20);
    const nameLen = buf.readUInt16LE(ptr + 28);
    const extraLen = buf.readUInt16LE(ptr + 30);
    const commentLen = buf.readUInt16LE(ptr + 32);
    const localOff = buf.readUInt32LE(ptr + 42);
    const name = buf.toString('utf8', ptr + 46, ptr + 46 + nameLen);
    ptr += 46 + nameLen + extraLen + commentLen;
    if (name.endsWith('/')) continue; // תיקייה
    // כותרת מקומית — אורך שם/extra יכול להיות שונה מהמרכז
    const lNameLen = buf.readUInt16LE(localOff + 26);
    const lExtraLen = buf.readUInt16LE(localOff + 28);
    const dataStart = localOff + 30 + lNameLen + lExtraLen;
    const raw = buf.subarray(dataStart, dataStart + compSize);
    let data;
    if (method === 0) data = Buffer.from(raw);
    else if (method === 8) data = zlib.inflateRawSync(raw);
    else throw new Error(`ZIP: שיטת דחיסה לא נתמכת (${method}) עבור ${name}`);
    files.push({ name: name.replace(/\\/g, '/'), data });
  }
  return files;
}

// ─────────── שכבה מטמורפית: טביעת-רגל שונה בכל הרצה (Polymorphism) ───────────
// כל הרצה מקבלת "מלח" אקראי שמעצב אחרת את צורת התעבורה והזיכרון: ריפוד
// אקראי לאירועים, ערבוב סדר החיבור לממסרים, וריצוד תזמון. התוצאה: אין שתי
// הרצות עם אותה חתימת-רשת/זיכרון — קשה מאוד לטבוע-אצבע, לסנן או לחסום.
const RUN_SALT = crypto.randomBytes(8).toString('hex');
const metaPad = () => crypto.randomBytes(3 + Math.floor(Math.random() * 29)).toString('base64');
const metaJitter = (base = 0, spread = 220) => base + Math.floor(Math.random() * spread);
function shuffle(arr) {
  const a = [...arr];
  for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
  return a;
}
/** חתימת ההרצה — משתנה בכל הפעלה (לניטור/הוכחת פולימורפיזם) */
const runFingerprint = () => hex(sha256(Buffer.from(RUN_SALT + ':' + process.pid))).slice(0, 16);

// ─────────── חלוקה א-סימטרית משוקללת לפי עומס (Θ) ───────────
// מפזר עבודה/חלקים בין יעדים ביחס לציון-הבריאות שלהם: יעד בריא יותר מקבל
// יותר, עמוס/חשוד מקבל פחות — ולא סיבוב שווה עיוור. דגימה משוקללת אקראית.
function pickWeighted(scored, k) {
  const pool = scored.filter((s) => s.score > 0).map((s) => ({ ...s }));
  const out = [];
  k = Math.min(k, pool.length);
  for (let n = 0; n < k; n++) {
    let total = pool.reduce((t, s) => t + s.score, 0);
    if (total <= 0) break;
    let r = Math.random() * total, idx = 0;
    for (; idx < pool.length; idx++) { r -= pool[idx].score; if (r <= 0) break; }
    out.push(pool[Math.min(idx, pool.length - 1)].id);
    pool.splice(Math.min(idx, pool.length - 1), 1);
  }
  return out;
}

// ════════════════════ SkyLattice-Λ: שכבת-הקוגניציה של המנוע ════════════════════
// שכבות מתמטיות שמייצרות את עצמן ומעדכנות את עצמן (כמו "משקולות"), עם נוסחאות,
// אלגוריתמים והסקה הסתברותית. אין כאן קסם — יש כאן מתמטיקה אמיתית של למידה
// מקוונת (online learning), שודדים רב-זרועיים (bandits), רגרסיה לוגיסטית,
// מכונת-נוסחאות בטוחה (בלי eval), בקר-הומאוסטזיס למודל-עצמי, ומיצוע פדרטיבי
// שמאפשר למשקולות "לחיות" ולהתכנס בין ה-peers של הרשת. הכול דטרמיניסטי-בזרע,
// חסום, וניתן-לבדיקה. המנוע מנטר ומבין את עצמו דרך המספרים האלה.

/** מחולל אקראי דטרמיניסטי (mulberry32) — זרע קבוע = הרצה נשנית לבדיקות */
function mulberry32(seed) {
  let a = seed >>> 0;
  return () => {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
const seedFromStr = (s) => { let h = 2166136261 >>> 0; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; };
const sigmoid = (z) => 1 / (1 + Math.exp(-Math.max(-60, Math.min(60, z))));
const clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;

/**
 * FormulaVM — מכונת-נוסחאות בטוחה: מקבלת נוסחה כטקסט בזמן ריצה, מפרקת (Shunting-
 * yard → RPN) ומחשבת — ללא eval, ללא גישה למערכת. כך "מקבלים ומפצחים נוסחאות"
 * כדי לשלוט בהתנהגות המנוע כמו מדיניות-כנתונים (policy-as-data).
 */
class FormulaVM {
  constructor() {
    this.fns = {
      abs: Math.abs, sqrt: Math.sqrt, exp: Math.exp, log: (x) => Math.log(Math.max(1e-12, x)),
      floor: Math.floor, ceil: Math.ceil, round: Math.round, sign: Math.sign, sin: Math.sin, cos: Math.cos, tanh: Math.tanh,
      min: Math.min, max: Math.max, pow: Math.pow, sigmoid,
      clamp: (x, lo, hi) => Math.min(hi, Math.max(lo, x)),
      if: (c, a, b) => (c ? a : b),
    };
    this.arity = { abs: 1, sqrt: 1, exp: 1, log: 1, floor: 1, ceil: 1, round: 1, sign: 1, sin: 1, cos: 1, tanh: 1, sigmoid: 1, min: 2, max: 2, pow: 2, clamp: 3, if: 3 };
    this.prec = { 'u-': 5, '^': 4, '*': 3, '/': 3, '%': 3, '+': 2, '-': 2, '<': 1, '>': 1, '<=': 1, '>=': 1, '==': 1, '!=': 1, '&&': 0, '||': 0 };
    this.right = { '^': true, 'u-': true };
  }
  _tokenize(s) {
    const t = []; let i = 0;
    const ops2 = ['<=', '>=', '==', '!=', '&&', '||'];
    while (i < s.length) {
      const c = s[i];
      if (c === ' ' || c === '\t' || c === '\n') { i++; continue; }
      if (/[0-9.]/.test(c)) { let j = i; while (j < s.length && /[0-9.eE+\-]/.test(s[j]) && !(/[+\-]/.test(s[j]) && !/[eE]/.test(s[j - 1]))) j++; t.push({ t: 'num', v: parseFloat(s.slice(i, j)) }); i = j; continue; }
      if (/[a-zA-Z_]/.test(c)) { let j = i; while (j < s.length && /[a-zA-Z0-9_]/.test(s[j])) j++; t.push({ t: 'name', v: s.slice(i, j) }); i = j; continue; }
      const two = s.slice(i, i + 2);
      if (ops2.includes(two)) { t.push({ t: 'op', v: two }); i += 2; continue; }
      if ('+-*/%^()<>,'.includes(c)) { t.push({ t: c === '(' ? 'lp' : c === ')' ? 'rp' : c === ',' ? 'comma' : 'op', v: c }); i++; continue; }
      throw new Error('FormulaVM: תו לא חוקי "' + c + '"');
    }
    return t;
  }
  _toRPN(tokens) {
    const out = [], ops = [];
    let prev = null;
    for (const tk of tokens) {
      if (tk.t === 'num') out.push(tk);
      else if (tk.t === 'name') {
        if (this.arity[tk.v] !== undefined) ops.push({ t: 'fn', v: tk.v });
        else out.push({ t: 'var', v: tk.v });
      } else if (tk.t === 'comma') {
        while (ops.length && ops[ops.length - 1].t !== 'lp') out.push(ops.pop());
        if (!ops.length) throw new Error('FormulaVM: פסיק לא במקומו');
      } else if (tk.t === 'op') {
        let op = tk.v;
        const unary = (op === '-' || op === '+') && (prev === null || prev.t === 'op' || prev.t === 'lp' || prev.t === 'comma');
        if (unary) op = op === '-' ? 'u-' : 'u+';
        if (op === 'u+') { prev = tk; continue; }
        const p = this.prec[op];
        while (ops.length) {
          const top = ops[ops.length - 1];
          if (top.t === 'fn') { out.push(ops.pop()); continue; }
          if (top.t !== 'op') break;
          const pt = this.prec[top.v];
          if (pt > p || (pt === p && !this.right[op])) out.push(ops.pop()); else break;
        }
        ops.push({ t: 'op', v: op });
      } else if (tk.t === 'lp') ops.push(tk);
      else if (tk.t === 'rp') {
        while (ops.length && ops[ops.length - 1].t !== 'lp') out.push(ops.pop());
        if (!ops.length) throw new Error('FormulaVM: סוגריים לא מאוזנים');
        ops.pop();
        if (ops.length && ops[ops.length - 1].t === 'fn') out.push(ops.pop());
      }
      prev = tk;
    }
    while (ops.length) { const o = ops.pop(); if (o.t === 'lp') throw new Error('FormulaVM: סוגריים לא מאוזנים'); out.push(o); }
    return out;
  }
  eval(formula, vars = {}) {
    const rpn = this._toRPN(this._tokenize(String(formula)));
    const st = [];
    const bin = (f) => { const b = st.pop(), a = st.pop(); if (a === undefined || b === undefined) throw new Error('FormulaVM: ביטוי חסר אופרנד'); st.push(f(a, b)); };
    for (const tk of rpn) {
      if (tk.t === 'num') st.push(tk.v);
      else if (tk.t === 'var') { if (!(tk.v in vars)) throw new Error('FormulaVM: משתנה לא מוגדר "' + tk.v + '"'); st.push(Number(vars[tk.v])); }
      else if (tk.t === 'fn') { const n = this.arity[tk.v], args = []; for (let k = 0; k < n; k++) args.unshift(st.pop()); st.push(this.fns[tk.v](...args)); }
      else {
        switch (tk.v) {
          case '+': bin((a, b) => a + b); break;
          case '-': bin((a, b) => a - b); break;
          case '*': bin((a, b) => a * b); break;
          case '/': bin((a, b) => a / b); break;
          case '%': bin((a, b) => a % b); break;
          case '^': bin((a, b) => Math.pow(a, b)); break;
          case 'u-': st.push(-st.pop()); break;
          case '<': bin((a, b) => (a < b ? 1 : 0)); break;
          case '>': bin((a, b) => (a > b ? 1 : 0)); break;
          case '<=': bin((a, b) => (a <= b ? 1 : 0)); break;
          case '>=': bin((a, b) => (a >= b ? 1 : 0)); break;
          case '==': bin((a, b) => (a === b ? 1 : 0)); break;
          case '!=': bin((a, b) => (a !== b ? 1 : 0)); break;
          case '&&': bin((a, b) => (a && b ? 1 : 0)); break;
          case '||': bin((a, b) => (a || b ? 1 : 0)); break;
          default: throw new Error('FormulaVM: אופרטור לא ידוע ' + tk.v);
        }
      }
    }
    if (st.length !== 1) throw new Error('FormulaVM: ביטוי לא תקין');
    return st[0];
  }
}

/**
 * AdaptiveLayer — שכבה עם וקטור-משקולות שמעדכן את עצמו: רגרסיה לוגיסטית מקוונת
 * (gradient descent פר-דוגמה). predict מחזיר הסתברות [0,1]; learn מזיז את
 * המשקולות לעבר האמת ומחזיר את ה-loss (entropy). המשקולות "נוצרות מעצמן" מזרע.
 */
class AdaptiveLayer {
  constructor(n, opts = {}) {
    const rnd = opts.rnd || mulberry32(opts.seed ?? 1);
    this.w = Array.from({ length: n }, () => (rnd() - 0.5) * 0.2);
    this.b = (rnd() - 0.5) * 0.2;
    this.lr = opts.lr ?? 0.1;
    this.l2 = opts.l2 ?? 1e-4;
  }
  raw(x) { let z = this.b; for (let i = 0; i < this.w.length; i++) z += this.w[i] * x[i]; return z; }
  predict(x) { return sigmoid(this.raw(x)); }
  learn(x, y) {
    const p = this.predict(x);
    const err = p - y;                               // נגזרת ה-cross-entropy מול sigmoid
    for (let i = 0; i < this.w.length; i++) this.w[i] -= this.lr * (err * x[i] + this.l2 * this.w[i]);
    this.b -= this.lr * err;
    const q = Math.min(1 - 1e-9, Math.max(1e-9, p));
    return -(y * Math.log(q) + (1 - y) * Math.log(1 - q));
  }
  weights() { return [this.b, ...this.w]; }
  loadWeights(v) { this.b = v[0]; this.w = v.slice(1); }
}

/**
 * Bandit — שודד רב-זרועי (UCB1): בוחר זרוע (למשל ממסר) לפי איזון תוחלת-תגמול
 * מול אי-ודאות (√(2·ln N / n_i)). לומד לבד לאיזה ממסר לשגר, ומתכנס למיטבי.
 */
class Bandit {
  constructor(arms, opts = {}) {
    this.arms = arms.slice();
    this.n = new Array(this.arms.length).fill(0);
    this.mean = new Array(this.arms.length).fill(0);
    this.total = 0;
    this.rnd = opts.rnd || mulberry32(opts.seed ?? 7);
  }
  choose() {
    for (let i = 0; i < this.arms.length; i++) if (this.n[i] === 0) return i; // חקירה ראשונית
    let best = 0, bv = -Infinity;
    for (let i = 0; i < this.arms.length; i++) {
      const ucb = this.mean[i] + Math.sqrt((2 * Math.log(this.total)) / this.n[i]);
      if (ucb > bv) { bv = ucb; best = i; }
    }
    return best;
  }
  reward(i, r) { this.n[i]++; this.total++; this.mean[i] += (r - this.mean[i]) / this.n[i]; }
  best() { let b = 0; for (let i = 1; i < this.mean.length; i++) if (this.mean[i] > this.mean[b]) b = i; return b; }
  ranking() { return this.arms.map((a, i) => ({ arm: a, mean: this.mean[i], n: this.n[i] })).sort((x, y) => y.mean - x.mean); }
}

/**
 * LayerStack — ערמת שכבות ותת-שכבות שמזינות זו את זו. forward מעביר את הווקטור
 * דרך כל השכבות (כל שכבה מפיקה תכונות חדשות מפלט הקודמת) ומחזיר וקטור חסום;
 * feedback מעדכן את *כל* השכבות לעבר יעד — למידה עמוקה-רדודה, self-updating.
 */
class LayerStack {
  constructor(widths, opts = {}) {
    const seed = opts.seed ?? seedFromStr(RUN_SALT);
    const rnd = mulberry32(seed);
    this.layers = [];
    for (let l = 0; l < widths.length - 1; l++) {
      const inN = widths[l], outN = widths[l + 1];
      const sub = Array.from({ length: outN }, () => new AdaptiveLayer(inN, { rnd, lr: opts.lr ?? 0.1 }));
      this.layers.push(sub); // כל תת-שכבה = נוירון לוגיסטי
    }
  }
  forward(x) {
    let v = x.slice();
    this.acts = [v];
    for (const sub of this.layers) { v = sub.map((neuron) => neuron.predict(v)); this.acts.push(v); }
    return v;
  }
  /** עדכון: השכבה האחרונה נלמדת מול היעד; הקודמות מקבלות אות-שגיאה מרוכך (מיצוע) */
  feedback(target) {
    const out = this.acts[this.acts.length - 1];
    let loss = 0;
    const last = this.layers[this.layers.length - 1];
    for (let k = 0; k < last.length; k++) loss += last[k].learn(this.acts[this.acts.length - 2], target[k] ?? 0);
    // אות-הדרכה אחורה מרוכך: תת-שכבות פנימיות נמשכות לעבר הפעילות הרצויה הממוצעת
    const tgtMean = target.reduce((a, b) => a + b, 0) / target.length;
    for (let l = this.layers.length - 2; l >= 0; l--) {
      const inAct = this.acts[l];
      for (const neuron of this.layers[l]) neuron.learn(inAct, tgtMean);
    }
    return loss / last.length;
  }
  weights() { return this.layers.map((sub) => sub.map((n) => n.weights())); }
  loadWeights(w) { this.layers.forEach((sub, l) => sub.forEach((n, k) => n.loadWeights(w[l][k]))); }
}

/** מיצוע פדרטיבי — כך משקולות "חיות" ומתכנסות בין peers של הרשת (federated avg) */
function fedAverage(vectors) {
  if (!vectors.length) return [];
  const n = vectors[0].length;
  const out = new Array(n).fill(0);
  for (const v of vectors) for (let i = 0; i < n; i++) out[i] += v[i];
  return out.map((s) => s / vectors.length);
}

/**
 * SelfModel — מודל-עצמי / "דופק": בקר-הומאוסטזיס שקורא טלמטריה פנימית ומפיק
 * חיוניות (vitality∈[0,1]), אנטרופיה (פיזור בריאות הממסרים), והחלטה אוטונומית.
 * זו "מודעות" במובן ההנדסי: המנוע מודד את מצבו ומגיב — לא תודעה מיסטית.
 */
class SelfModel {
  constructor() { this.history = []; this.vit = 1; }
  static entropy(scores) {
    const pos = scores.filter((s) => s > 0);
    const sum = pos.reduce((a, b) => a + b, 0);
    if (sum <= 0) return 0;
    let h = 0; for (const s of pos) { const p = s / sum; h -= p * Math.log2(p); }
    const max = Math.log2(pos.length || 1);
    return max > 0 ? h / max : 0; // מנורמל [0,1]: 1=פיזור מלא (בריא), 0=ריכוז/שבירות
  }
  observe(tel) {
    const relaysUp = tel.relaysUp ?? 0;
    const phiAvg = tel.phiAvg ?? 0;
    const snapFresh = clamp01(tel.snapFresh ?? 1);
    const backlog = tel.backlog ?? 0;
    const conn = clamp01(relaysUp / 6);
    const calm = clamp01(1 - phiAvg / 8);
    const flow = clamp01(1 - backlog / 20);
    const ent = SelfModel.entropy(tel.scores || []);
    // חיוניות = ממוצע משוקלל של האיתותים; מוחלק אקספוננציאלית (EWMA) לדופק יציב
    const inst = clamp01(0.34 * conn + 0.28 * calm + 0.18 * flow + 0.12 * snapFresh + 0.08 * ent);
    this.vit = 0.6 * this.vit + 0.4 * inst;
    let action = 'steady';
    if (relaysUp < 3 || phiAvg > 6) action = 'hop';
    else if (snapFresh < 0.5 || ent < 0.4) action = 'heal';
    else if (backlog > 12) action = 'throttle';
    const pulse = { vitality: +this.vit.toFixed(4), instant: +inst.toFixed(4), entropy: +ent.toFixed(4), phiAvg, relaysUp, action, ts: Date.now() };
    this.history.push(pulse); if (this.history.length > 240) this.history.shift();
    return pulse;
  }
  last() { return this.history[this.history.length - 1] || { vitality: this.vit, action: 'steady' }; }
}

// ════════════════ SkyLattice-Ξ: רשת-החישוב הדטרמיניסטית (Computation Lattice) ════
// רשת קטנה-נוספת שמתחברת לרשת המבוזרת ומקבלת "משימות-נוסחה/אלגוריתם" כנתונים,
// פותרת אותן, ושומרת את התשובה ברשת לצמיתות — ממוענת-לפי-תוכן (content-addressed).
//
// התובנה שפורצת קדימה (בלי לשבור פיזיקה): פונקציה *טהורה ודטרמיניסטית* היא
// ניתנת-למטמון. לכן jobId = sha256(תוכנית+קלט), והתוצאה היא פונקציה של jobId.
// מי שפעיל *פעם אחת* פותר; התשובה מתפרסמת ברשת עם ה-jobId → מאותו רגע היא
// מוגשת לכולם *עם אפס פותרים חיים*. אמינות דרך "הוכחה-בשכפול": מקבלים תוצאה
// רק כש-k פותרים בלתי-תלויים פרסמו את אותו digest (עמיד-ביזנטית).
//
// זה מרחיב את מודל "ללא-מפעיל" מ"קריאות אפויות-מראש" ל"כל חישוב טהור — חופשי
// מהרשת אחרי פתירה אחת". חישוב עם תופעות-לוואי עדיין דורש מעבד; זה לא נשבר.

const XI_LIMITS = { steps: 5_000_000, outBytes: 262_144, matrix: 64, iters: 2_000_000, listLen: 4096 };
const _xiFormula = new FormulaVM();

/** רישום אופרטורים טהורים בלבד — אין eval, אין I/O, אין גישה למערכת; כולם חסומים */
const XI_OPS = {
  // נוסחה: מעריך ביטוי מתמטי בטוח מעל וקטור/מפת-קלט
  formula: ({ expr, vars = {} }) => {
    if (typeof expr !== 'string') throw new Error('Ξ formula: expr חייב להיות מחרוזת');
    return _xiFormula.eval(expr, vars);
  },
  // שרשרת-גיבוב: sha256 מאותחל n פעמים — עבודה דטרמיניסטית מדידה (VDF-כמו)
  hashChain: ({ seed = '', iters = 1 }) => {
    iters = Math.floor(iters);
    if (!(iters >= 1 && iters <= XI_LIMITS.iters)) throw new Error('Ξ hashChain: iters מחוץ לתחום');
    let h = sha256(Buffer.from(String(seed)));
    for (let i = 1; i < iters; i++) h = sha256(h);
    return hex(h);
  },
  // כפל-מטריצות מעל שדה הממשיים (חסום בגודל) — אלגברה לינארית טהורה
  matmul: ({ a, b }) => {
    if (!Array.isArray(a) || !Array.isArray(b)) throw new Error('Ξ matmul: קלט לא תקין');
    const n = a.length, m = a[0].length, p = b[0].length;
    if (n > XI_LIMITS.matrix || m > XI_LIMITS.matrix || p > XI_LIMITS.matrix) throw new Error('Ξ matmul: מטריצה גדולה מדי');
    if (b.length !== m) throw new Error('Ξ matmul: ממדים לא תואמים');
    const out = Array.from({ length: n }, () => new Array(p).fill(0));
    for (let i = 0; i < n; i++) for (let k = 0; k < m; k++) { const aik = a[i][k]; for (let j = 0; j < p; j++) out[i][j] += aik * b[k][j]; }
    return out;
  },
  // פירוק-לגורמים (חלוקת-ניסיון) — תורת-המספרים, דטרמיניסטי לחלוטין
  factorize: ({ n }) => {
    n = Math.floor(Number(n));
    if (!(n >= 2 && n <= Number.MAX_SAFE_INTEGER)) throw new Error('Ξ factorize: n מחוץ לתחום');
    const f = []; let x = n, steps = 0;
    for (let d = 2; d * d <= x; d++) { while (x % d === 0) { f.push(d); x = x / d; if (++steps > XI_LIMITS.steps) throw new Error('Ξ factorize: חריגת-צעדים'); } }
    if (x > 1) f.push(x);
    return f;
  },
  // סינון-ראשוניים עד N (Eratosthenes) — מחזיר ספירה ואת ה-ראשוני ה-k
  primes: ({ upTo }) => {
    upTo = Math.floor(Number(upTo));
    if (!(upTo >= 2 && upTo <= 5_000_000)) throw new Error('Ξ primes: upTo מחוץ לתחום');
    const sieve = new Uint8Array(upTo + 1);
    let count = 0, last = 0;
    for (let i = 2; i <= upTo; i++) { if (!sieve[i]) { count++; last = i; for (let j = i * i; j <= upTo; j += i) sieve[j] = 1; } }
    return { count, largest: last };
  },
  // צמצום-רשימה מספרי טהור: sum/prod/min/max/mean
  reduce: ({ op, list }) => {
    if (!Array.isArray(list) || list.length > XI_LIMITS.listLen) throw new Error('Ξ reduce: רשימה לא תקינה');
    const xs = list.map(Number);
    switch (op) {
      case 'sum': return xs.reduce((a, b) => a + b, 0);
      case 'prod': return xs.reduce((a, b) => a * b, 1);
      case 'min': return Math.min(...xs);
      case 'max': return Math.max(...xs);
      case 'mean': return xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0;
      default: throw new Error('Ξ reduce: op לא ידוע');
    }
  },
};

/** קנוניזציה יציבה של JSON (מפתחות ממוינים) → אותו קלט = אותם בייטים = אותו jobId */
function xiCanon(v) {
  if (v === null || typeof v !== 'object') return JSON.stringify(v);
  if (Array.isArray(v)) return '[' + v.map(xiCanon).join(',') + ']';
  return '{' + Object.keys(v).sort().map((k) => JSON.stringify(k) + ':' + xiCanon(v[k])).join(',') + '}';
}
/** מזהה-משימה ממוען-לפי-תוכן: תלוי אך ורק בתוכנית ובקלט (op+args) */
function xiJobId(program) {
  if (!program || typeof program.op !== 'string') throw new Error('Ξ: תוכנית חייבת {op, args}');
  return hex(sha256(Buffer.from('skyxi:v1:' + xiCanon({ op: program.op, args: program.args ?? {} })))).slice(0, 40);
}
/** פתירה מקומית טהורה — מריצה את האופרטור הרשום ומחזירה תוצאה + digest */
function xiSolve(program) {
  const fn = XI_OPS[program.op];
  if (!fn) throw new Error('Ξ: אופרטור לא רשום "' + program.op + '"');
  const result = fn(program.args ?? {});
  const digest = xiDigest(result);
  if (Buffer.byteLength(xiCanon(result)) > XI_LIMITS.outBytes) throw new Error('Ξ: פלט גדול מדי');
  return { result, digest };
}
/** digest של תוצאה — חתימת-תוכן דטרמיניסטית להשוואת-שכפול */
function xiDigest(result) { return hex(sha256(Buffer.from('skyxi:res:' + xiCanon(result)))).slice(0, 32); }

/**
 * xiQuorum — "הוכחה-בשכפול": מקבל אוסף תוצאות מפותרים שונים ({author,digest,result})
 * ומחזיר תוצאה רק כאשר ≥k *מחברים ייחודיים* מסכימים על אותו digest. עמיד לפותר
 * זדוני בודד: הוא לא יכול לזייף הסכמה של k צמתים בלתי-תלויים.
 */
function xiQuorum(results, k = 2) {
  const byDigest = new Map();
  for (const r of results) {
    if (!r || !r.digest) continue;
    if (!byDigest.has(r.digest)) byDigest.set(r.digest, { digest: r.digest, result: r.result, authors: new Set() });
    byDigest.get(r.digest).authors.add(r.author ?? r.result_author ?? Math.random());
  }
  let best = null;
  for (const g of byDigest.values()) { const n = g.authors.size; if (!best || n > best.agree) best = { digest: g.digest, result: g.result, agree: n }; }
  if (best && best.agree >= k) return { ...best, ok: true };
  return { ok: false, agree: best ? best.agree : 0, need: k };
}

// ═══════════ SkyLattice-Ξ/DAG: הרכבת-משימות — צנרת-חישוב שמצטברת ברשת ═══════════
// גרף מכוון חסר-מעגלים של משימות טהורות: הפלט של צומת אחד הוא הקלט של הבא.
// כיוון שכל צומת ממוען-לפי-תוכן *אחרי* הצבת הקלטים מהמעלה-זרם, כל הצנרת
// דטרמיניסטית וניתנת-למטמון — תוצאות-הביניים נשמרות ברשת ומצטברות, והרצה
// חוזרת של אותה צנרת היא פגיעת-מטמון בלבד. הפניה: {"$ref":"nodeId","path":"a.b"}.

/** מאתר את כל ה-nodeId שצומת תלוי בהם (סורק $ref בעומק) */
function xiRefs(args) {
  const ids = new Set();
  const walk = (v) => {
    if (!v || typeof v !== 'object') return;
    if (typeof v.$ref === 'string') { ids.add(v.$ref); return; }
    for (const k of Object.keys(v)) walk(v[k]);
  };
  walk(args);
  return [...ids];
}
/** בורר ערך מתוך תוצאה לפי נתיב "a.b.0" (path אופציונלי) */
function xiPick(result, pathStr) {
  if (!pathStr) return result;
  let cur = result;
  for (const seg of String(pathStr).split('.')) { if (cur == null) return undefined; cur = cur[seg]; }
  return cur;
}
/** מציב תוצאות-מעלה-זרם במקום placeholders של $ref (deep) */
function xiSubst(args, resolved) {
  if (!args || typeof args !== 'object') return args;
  if (typeof args.$ref === 'string') {
    if (!(args.$ref in resolved)) throw new Error('Ξ/DAG: הפניה לצומת לא-פתור "' + args.$ref + '"');
    return xiPick(resolved[args.$ref], args.path);
  }
  if (Array.isArray(args)) return args.map((v) => xiSubst(v, resolved));
  const out = {};
  for (const k of Object.keys(args)) out[k] = xiSubst(args[k], resolved);
  return out;
}
// ── DAG מותנה/מסועף: ענפים נבחרים לפי תוצאות-ביניים דרך FormulaVM ──
// סוגי-צומת: (א) חישוב {op,args}; (ב) שער {when:{expr,vars}} — מדלג אם שקר;
// (ג) בורר {branch:{on,vars,cases,default}} — בוחר ערך לפי החלטת-נוסחה;
// (ד) איחוד {coalesce:[refs]} — הערך הראשון שלא-דולג. דילוג מתפשט: צומת-חישוב
// שאחת מתלויותיו דולגה — מדלג בעצמו (גיזום ענפים אמיתי), למעט coalesce.
const XI_SKIP = Symbol('xi-skip');

/** כל ה-nodeId שצומת תלוי בהם — כולל args, when.vars, branch, coalesce, value */
function xiNodeRefs(node) {
  return xiRefs({ a: node.args, w: node.when && node.when.vars, b: node.branch, c: node.coalesce, v: node.value });
}
/** האם אחת מתלויות-ה-args של הצומת דולגה (SKIP) — ואז אי-אפשר לחשב */
function xiHasSkip(args, resolved) {
  let bad = false;
  const walk = (v) => { if (bad || !v || typeof v !== 'object') return; if (typeof v.$ref === 'string') { if (resolved[v.$ref] === XI_SKIP) bad = true; return; } for (const k of Object.keys(v)) walk(v[k]); };
  walk(args);
  return bad;
}
/** מציב משתני-נוסחה (name→value|$ref); ref שדולג נחשב 0 (false) בהערכה */
function xiSubstVars(vars, resolved) {
  const o = {};
  for (const k of Object.keys(vars || {})) { let v = xiSubst(vars[k], resolved); if (v === XI_SKIP) v = 0; o[k] = v; }
  return o;
}
/** אימות + מיון-טופולוגי (Kahn) עם זיהוי-מעגלים; מחזיר סדר-ריצה של nodeId */
function xiDagPlan(dag) {
  if (!dag || typeof dag.nodes !== 'object' || !dag.nodes) throw new Error('Ξ/DAG: חסר nodes');
  const ids = Object.keys(dag.nodes);
  if (!ids.length) throw new Error('Ξ/DAG: גרף ריק');
  if (dag.output && !dag.nodes[dag.output]) throw new Error('Ξ/DAG: output מצביע על צומת לא-קיים');
  const indeg = new Map(ids.map((id) => [id, 0]));
  const edges = new Map(ids.map((id) => [id, []]));
  for (const id of ids) {
    const node = dag.nodes[id];
    if (!node || (typeof node.op !== 'string' && !node.branch && !node.coalesce && !('value' in node))) throw new Error('Ξ/DAG: צומת "' + id + '" חייב op / branch / coalesce / value');
    for (const dep of xiNodeRefs(node)) {
      if (!dag.nodes[dep]) throw new Error('Ξ/DAG: צומת "' + id + '" תלוי בצומת לא-קיים "' + dep + '"');
      edges.get(dep).push(id); indeg.set(id, indeg.get(id) + 1);
    }
  }
  const queue = ids.filter((id) => indeg.get(id) === 0).sort();
  const order = [];
  while (queue.length) {
    const id = queue.shift(); order.push(id);
    for (const nxt of edges.get(id)) { indeg.set(nxt, indeg.get(nxt) - 1); if (indeg.get(nxt) === 0) { queue.push(nxt); queue.sort(); } }
  }
  if (order.length !== ids.length) throw new Error('Ξ/DAG: זוהה מעגל — הגרף חייב להיות חסר-מעגלים (DAG)');
  return order;
}
/** מזהה-צנרת ממוען-לפי-תוכן — תלוי במבנה הגרף כולו */
function xiDagId(dag) { return hex(sha256(Buffer.from('skyxi:dag:v1:' + xiCanon({ nodes: dag.nodes, output: dag.output ?? null })))).slice(0, 40); }
/**
 * הרצת DAG טהורה עם פותר מוזרק (solveFn(program)->{result,digest}). תומך בשערים
 * מותנים, ברירה מסועפת ואיחוד — עם גיזום-ענפים אמיתי (דילוג לא מחשב). מחזיר
 * תוצאות, jobId, פלט, digest מצטבר, וסדר-ריצה. ניתן-לבדיקה לחלוטין בלי רשת.
 */
async function xiDagEval(dag, solveFn, opts = {}) {
  const order = xiDagPlan(dag);
  const vm = _xiFormula;
  const memo = opts.memo || new Map(); // מזכר תוך-ריצה: jobId זהה נפתר פעם אחת
  const resolved = {}, jobIds = {}, digests = {}, sources = {}, skipped = [];
  for (const id of order) {
    const node = dag.nodes[id];
    // (ב) שער מותנה — אם הנוסחה שקרית, הצומת (וכל תת-הענף שתלוי בו) מדולג
    if (node.when) {
      const ok = vm.eval(node.when.expr, xiSubstVars(node.when.vars, resolved));
      if (!ok) { resolved[id] = XI_SKIP; digests[id] = 'SKIP'; jobIds[id] = null; sources[id] = 'skip'; skipped.push(id); continue; }
    }
    // (ה) ערך/קבוע — קלט מוזרק בזמן-ריצה או אליאס ל-$ref (מאפשר "אתר-חי מחושב")
    if ('value' in node) {
      const val = xiSubst(node.value, resolved);
      resolved[id] = val; digests[id] = val === XI_SKIP ? 'SKIP' : xiDigest(val); jobIds[id] = null; sources[id] = 'value';
      if (val === XI_SKIP) skipped.push(id);
      continue;
    }
    // (ד) איחוד — הערך הראשון שלא-דולג (בורר את הענף שרץ בפועל)
    if (node.coalesce) {
      let val = XI_SKIP;
      for (const ref of node.coalesce) { const v = xiSubst(ref, resolved); if (v !== XI_SKIP && v !== undefined) { val = v; break; } }
      resolved[id] = val; digests[id] = val === XI_SKIP ? 'SKIP' : xiDigest(val); jobIds[id] = null; sources[id] = 'meta';
      if (val === XI_SKIP) skipped.push(id);
      continue;
    }
    // (ג) בורר מסועף — נוסחת-החלטה בוחרת איזה case/default לאמץ
    if (node.branch) {
      const b = node.branch;
      const key = String(vm.eval(b.on, xiSubstVars(b.vars, resolved)));
      const chosen = (b.cases && Object.prototype.hasOwnProperty.call(b.cases, key)) ? b.cases[key] : b.default;
      const val = chosen === undefined ? XI_SKIP : xiSubst(chosen, resolved);
      resolved[id] = val; digests[id] = val === XI_SKIP ? 'SKIP' : xiDigest(val); jobIds[id] = null; sources[id] = 'meta';
      if (val === XI_SKIP) skipped.push(id);
      continue;
    }
    // (א) חישוב — אם תלות דולגה, מדלגים (גיזום); אחרת פותרים (עם מזכר)
    if (xiHasSkip(node.args ?? {}, resolved)) { resolved[id] = XI_SKIP; digests[id] = 'SKIP'; jobIds[id] = null; sources[id] = 'skip'; skipped.push(id); continue; }
    const program = { op: node.op, args: xiSubst(node.args ?? {}, resolved) };
    const jobId = xiJobId(program);
    let out;
    if (memo.has(jobId)) { out = memo.get(jobId); out = { ...out, source: 'memo' }; }
    else { out = await solveFn(program, id); memo.set(jobId, out); }
    resolved[id] = out.result; jobIds[id] = jobId; digests[id] = out.digest; sources[id] = out.source || 'computed';
  }
  const outId = dag.output ?? order[order.length - 1];
  const outVal = resolved[outId];
  const digest = hex(sha256(Buffer.from('skyxi:dagres:' + xiCanon(order.map((id) => digests[id]))))).slice(0, 32);
  const results = {}; for (const id of order) results[id] = resolved[id] === XI_SKIP ? null : resolved[id];
  return { output: outVal === XI_SKIP ? null : outVal, outputSkipped: outVal === XI_SKIP, outId, results, jobIds, digests, sources, dagDigest: digest, order, skipped };
}

// ═══════════ SkyLattice-∇: הרצת-מפרט אצל הלקוח (עוקף "עובד או מטמון-חם") ═══════════
// התובנה: לחישוב טהור אין חובה שמשהו יפתור *מראש* כל סט-קלטים. מספיק שהרשת
// תשמור את *התוכנית* (מפרט-DAG/מדיניות), וכל מבקר/צומת יריץ אותה מקומית על
// הקלטים שלו. אין עובד-Ω, אין צורך במטמון חם לאותו סט — רק שליפת-מפרט (נתונים)
// + CPU של הלקוח. זה לא מבטל פיזיקה; זה מעביר את המעבד אל המבקר.

const XI_LIGHT_OPS = new Set(['formula', 'reduce', 'factorize', 'primes', 'matmul', 'hashChain']);

/** מנתח DAG → מדיניות ניידת להרצה אצל לקוח (דפדפן/Node בלי רשת-חישוב) */
function xiCollapse(dag) {
  const order = xiDagPlan(dag);
  const ops = new Set();
  let light = true;
  for (const id of order) {
    const n = dag.nodes[id];
    if (n.op) {
      if (!XI_OPS[n.op]) throw new Error('∇: אופרטור לא נתמך ללקוח "' + n.op + '"');
      ops.add(n.op);
      if (!XI_LIGHT_OPS.has(n.op)) light = false;
    }
  }
  return { v: 1, kind: 'client-dag', light, ops: [...ops], dag: { nodes: dag.nodes, output: dag.output ?? null }, dagId: xiDagId(dag) };
}

/**
 * הרצה מקומית טהורה של DAG+inputs — בלי compute/רשת. זה הלב של ∇:
 * כל סט-קלטים שלא נראה מעולם עדיין ניתן לחישוב אם המפרט ביד.
 */
async function xiClientEval(dag, inputs = {}) {
  let d = dag;
  if (inputs && typeof inputs === 'object' && Object.keys(inputs).length) {
    d = { ...dag, nodes: { ...dag.nodes } };
    for (const k of Object.keys(inputs)) d.nodes[k] = { value: inputs[k] };
  }
  const ev = await xiDagEval(d, (program) => Promise.resolve({ ...xiSolve(program), source: 'client-eval' }));
  return { ...ev, source: 'client-eval', dagId: xiDagId(d) };
}

/** מאתר הפניות למדיניות/צנרת מקוננות: {$policy:'name'} / {$dag:{...}} */
function xiPolicyRefs(args, out = []) {
  if (!args || typeof args !== 'object') return out;
  if (typeof args.$policy === 'string') { out.push({ type: 'policy', name: args.$policy }); return out; }
  if (args.$dag && typeof args.$dag === 'object') { out.push({ type: 'dag', dag: args.$dag }); return out; }
  for (const k of Object.keys(args)) xiPolicyRefs(args[k], out);
  return out;
}

/**
 * מרחיב DAG עם מדיניות מקוננת: מחליף {$policy} / {$dag} בערך מחושב של תת-צנרת.
 * fetchPolicy(name)→{dag} מוזרק (רשת או ספרייה מקומית).
 */
async function xiExpandNested(dag, fetchPolicy) {
  const nodes = { ...dag.nodes };
  for (const id of Object.keys(nodes)) {
    const n = nodes[id];
    const refs = xiPolicyRefs({ a: n.args, v: n.value, b: n.branch, c: n.coalesce });
    if (!refs.length) continue;
    // צומת-ערך שמפנה למדיניות → מריצים את תת-הצנרת ומציבים את הפלט
    if (n.value && typeof n.value.$policy === 'string') {
      const pol = await fetchPolicy(n.value.$policy);
      if (!pol?.dag) throw new Error('∇ nested: מדיניות חסרה "' + n.value.$policy + '"');
      const sub = await xiClientEval(pol.dag, n.value.inputs || {});
      nodes[id] = { value: sub.output };
    } else if (n.value && n.value.$dag) {
      const sub = await xiClientEval(n.value.$dag, n.value.inputs || {});
      nodes[id] = { value: sub.output };
    } else if (n.op === 'policy' || n.op === 'nested') {
      const name = n.args?.name || n.args?.policy;
      const pol = await fetchPolicy(name);
      if (!pol?.dag) throw new Error('∇ nested: מדיניות חסרה "' + name + '"');
      const sub = await xiClientEval(pol.dag, n.args?.inputs || {});
      nodes[id] = { value: sub.output };
    }
  }
  return { nodes, output: dag.output };
}

/** Door 9: אם כל הצנרת ניתנת לצמצום לביטוי FormulaVM יחיד — מחזירים formula-tree */
function xiCollapseFormula(dag) {
  try {
    const order = xiDagPlan(dag);
    const exprs = {}; // nodeId -> formula string over inputs
    const isNum = (v) => typeof v === 'number' && Number.isFinite(v);
    for (const id of order) {
      const n = dag.nodes[id];
      if (n.when || n.branch || n.coalesce) return null; // הסתעפות — לא נוסחה יחידה פשוטה
      if ('value' in n) {
        if (isNum(n.value)) exprs[id] = String(n.value);
        else if (n.value && typeof n.value.$ref === 'string') exprs[id] = exprs[n.value.$ref];
        else return null;
        continue;
      }
      if (n.op === 'formula' && typeof n.args?.expr === 'string') {
        let e = n.args.expr;
        const vars = n.args.vars || {};
        for (const k of Object.keys(vars)) {
          const v = vars[k];
          let rep;
          if (isNum(v)) rep = String(v);
          else if (v && typeof v.$ref === 'string') rep = '(' + (exprs[v.$ref] ?? k) + ')';
          else return null;
          e = e.replace(new RegExp('\\b' + k + '\\b', 'g'), rep);
        }
        exprs[id] = '(' + e + ')';
        continue;
      }
      return null; // אופרטור כבד — לא מתכנס לנוסחה
    }
    const outId = dag.output ?? order[order.length - 1];
    if (!exprs[outId]) return null;
    return { kind: 'formula-tree', expr: exprs[outId], dagId: xiDagId(dag) };
  } catch { return null; }
}

/** Door 9 upgrade to xiCollapse — מנסה formula-tree קודם */
function xiCollapseFull(dag) {
  const base = xiCollapse(dag);
  const ft = xiCollapseFormula(dag);
  if (ft) return { ...base, ...ft, light: true, collapsed: true };
  return { ...base, collapsed: false };
}

/**
 * Door 11: שפת-מדיניות מוצהרת (DSL) → DAG.
 * תחביר שורות:
 *   policy <name>
 *   in <id> = <number|string>
 *   <id> = formula <expr> [when <cond>] [where a<-x,b<-y]
 *   <id> = factorize <ref|num> [when <cond>] [where ...]
 *   <id> = primes <ref|num> ...
 *   <id> = coalesce a, b, c
 *   <id> = branch <expr> { key:ref, ... } default:ref   (אופציונלי פשוט: skip)
 *   output <id>
 */
function compilePolicyDSL(text) {
  const lines = String(text).split(/\r?\n/).map((l) => l.replace(/#.*$/, '').trim()).filter(Boolean);
  const nodes = {};
  let name = 'policy', output = null;
  const parseWhere = (s) => {
    const vars = {};
    if (!s) return vars;
    for (const part of s.split(',')) {
      const m = part.trim().match(/^(\w+)\s*<-\s*(\w+)(?:\.(\w+))?$/);
      if (!m) continue;
      vars[m[1]] = m[3] ? { $ref: m[2], path: m[3] } : { $ref: m[2] };
    }
    return vars;
  };
  const parseWhen = (rest) => {
    const wm = rest.match(/\bwhen\s+(.+?)(?:\s+where\s+|$)/);
    const wh = rest.match(/\bwhere\s+(.+)$/);
    return { whenExpr: wm ? wm[1].trim() : null, where: parseWhere(wh ? wh[1] : '') };
  };
  for (const line of lines) {
    let m;
    if ((m = line.match(/^policy\s+(\S+)$/i))) { name = m[1]; continue; }
    if ((m = line.match(/^output\s+(\w+)$/i))) { output = m[1]; continue; }
    if ((m = line.match(/^in\s+(\w+)\s*=\s*(.+)$/i))) {
      const raw = m[2].trim();
      const num = Number(raw);
      nodes[m[1]] = { value: Number.isFinite(num) && String(num) === raw ? num : raw.replace(/^["']|["']$/g, '') };
      continue;
    }
    if ((m = line.match(/^(\w+)\s*=\s*coalesce\s+(.+)$/i))) {
      nodes[m[1]] = { coalesce: m[2].split(',').map((x) => ({ $ref: x.trim() })) };
      continue;
    }
    if ((m = line.match(/^(\w+)\s*=\s*formula\s+(.+)$/i))) {
      const id = m[1]; let rest = m[2].trim();
      const wh = rest.match(/\bwhere\s+(.+)$/); const wm = rest.match(/\bwhen\s+(.+?)(?:\s+where\s+|$)/);
      let expr = rest;
      if (wm) expr = rest.slice(0, rest.indexOf('when')).trim();
      else if (wh) expr = rest.slice(0, rest.indexOf('where')).trim();
      const vars = parseWhere(wh ? wh[1] : '');
      const node = { op: 'formula', args: { expr, vars } };
      if (wm) node.when = { expr: wm[1].trim(), vars: { ...vars } };
      nodes[id] = node;
      continue;
    }
    if ((m = line.match(/^(\w+)\s*=\s*(factorize|primes|reduce)\s+(.+)$/i))) {
      const id = m[1], op = m[2].toLowerCase(); let rest = m[3].trim();
      const { whenExpr, where } = parseWhen(rest);
      rest = rest.replace(/\bwhen\b.*/, '').replace(/\bwhere\b.*/, '').trim();
      let args;
      if (op === 'factorize') {
        const n = Number(rest);
        args = { n: Number.isFinite(n) && String(n) === rest ? n : (where.n || { $ref: rest }) };
      } else if (op === 'primes') {
        const n = Number(rest);
        args = { upTo: Number.isFinite(n) && String(n) === rest ? n : (where.upTo || { $ref: rest }) };
      } else {
        const parts = rest.split(/\s+/); args = { op: parts[0], list: { $ref: parts[1] || parts[0] } };
      }
      const node = { op, args };
      if (whenExpr) node.when = { expr: whenExpr, vars: Object.keys(where).length ? where : { x: args.n || args.upTo } };
      nodes[id] = node;
      continue;
    }
    throw new Error('DSL: שורה לא מזוהה: ' + line);
  }
  if (!Object.keys(nodes).length) throw new Error('DSL: אין צמתים');
  if (!output) output = Object.keys(nodes).pop();
  return { name: slugify(name) || 'policy', dag: { nodes, output } };
}

/**
 * Door 13: מכונת-מצבים טהורה — fold של אירועים → מצב דטרמיניסטי זהה אצל כל לקוח.
 * spec: { initial, transitions:[{from,on,to,emit?}], emit?:fn }
 */
function stateFold(spec, events = []) {
  if (!spec || typeof spec.initial !== 'string') throw new Error('stateFold: חסר initial');
  const trans = Array.isArray(spec.transitions) ? spec.transitions : [];
  let state = spec.initial;
  const emitted = [];
  for (const ev of events) {
    const on = typeof ev === 'string' ? ev : ev?.type;
    const t = trans.find((x) => (x.from === state || x.from === '*') && x.on === on);
    if (!t) continue; // אירוע לא-רלוונטי — מתעלמים (idempotent skip)
    state = t.to;
    if (t.emit !== undefined) emitted.push(typeof t.emit === 'function' ? t.emit(ev, state) : t.emit);
  }
  return { state, emitted, steps: events.length };
}

/** digest דטרמיניסטי של מצב — לאימות בין לקוחות */
function stateDigest(folded) { return xiDigest({ state: folded.state, emitted: folded.emitted }); }

/**
 * Door 12: שוק חישוב טהור — דירוג הצעות לפי (priority, price, agree).
 * bids: [{solver, digest, result, price, priority, ts}]
 */
function marketPick(bids, opts = {}) {
  const list = (bids || []).filter((b) => b && b.digest != null);
  if (!list.length) return { ok: false };
  // קודם קוורום על digest; אחר כך מינימום מחיר / מקסימום עדיפות
  const q = xiQuorum(list.map((b) => ({ author: b.solver || b.author, digest: b.digest, result: b.result })), opts.quorum || 1);
  if (!q.ok) return { ok: false, agree: q.agree, need: q.need };
  const winners = list.filter((b) => b.digest === q.digest);
  winners.sort((a, b) => (b.priority || 0) - (a.priority || 0) || (a.price || 0) - (b.price || 0) || (a.ts || 0) - (b.ts || 0));
  return { ok: true, ...winners[0], agree: q.agree, result: q.result, digest: q.digest };
}

/**
 * Door 15+: זיהוי-תרחישים — המנוע מסווג את מצבו ופותח פעולות/דלתות בהתאם.
 * תרחישים: cold_start, relay_degraded, cache_hot, identity_present, policy_stale,
 * high_load, healthy_steady, partition_risk…
 */
class ScenarioDetector {
  constructor() { this.last = null; this.history = []; }
  observe(tel = {}) {
    const relaysUp = tel.relaysUp ?? 0;
    const phiAvg = tel.phiAvg ?? 0;
    const vitality = tel.vitality ?? 1;
    const nCached = tel.nCached ?? 0;
    const nComputed = tel.nComputed ?? 0;
    const hasIdentity = !!tel.hasIdentity;
    const policyAge = tel.policyAgeMs ?? 0;
    const backlog = tel.backlog ?? 0;
    const logicFork = !!tel.logicFork;
    const dbLagMs = tel.dbLagMs ?? 0;
    let scenario = 'healthy_steady', confidence = 0.6, action = 'steady', activePolicy = 'policy:full';
    if (logicFork) { scenario = 'logic_fork'; confidence = 0.95; action = 'freeze_audit'; activePolicy = 'policy:degraded'; }
    else if (relaysUp < 2 || phiAvg > 7) { scenario = 'relay_degraded'; confidence = 0.9; action = 'swap_degraded'; activePolicy = 'policy:degraded'; }
    else if (dbLagMs > 30_000) { scenario = 'db_lag'; confidence = 0.88; action = 'throttle'; activePolicy = 'policy:degraded'; } // S8
    else if (relaysUp >= 2 && nCached === 0 && nComputed === 0 && policyAge === 0) { scenario = 'cold_start'; confidence = 0.75; action = 'publish_warm'; activePolicy = 'policy:full'; }
    else if (nCached > 0 && nCached >= nComputed) { scenario = 'cache_hot'; confidence = 0.8; action = 'client_prefer'; activePolicy = 'policy:full'; }
    else if (hasIdentity) { scenario = 'identity_present'; confidence = 0.7; action = 'branch_identity'; activePolicy = 'policy:full'; }
    else if (policyAge > 3_600_000) { scenario = 'policy_stale'; confidence = 0.85; action = 'republish_policy'; activePolicy = 'policy:full'; }
    else if (backlog > 10 || vitality < 0.4) { scenario = 'high_load'; confidence = 0.8; action = 'throttle'; activePolicy = 'policy:degraded'; }
    else if (SelfModel.entropy(tel.scores || []) < 0.35 && relaysUp > 0) { scenario = 'partition_risk'; confidence = 0.7; action = 'heal'; activePolicy = 'policy:degraded'; }
    const out = { scenario, confidence, action, activePolicy, ts: Date.now(), relaysUp, vitality, dbLagMs };
    this.last = out; this.history.push(out); if (this.history.length > 120) this.history.shift();
    return out;
  }
}

/** הקשר לכתובת חיה לפי נוסחה (Door 14): שעה/שפה/משתמש/… */
function liveContext(extra = {}) {
  const d = new Date();
  return {
    hour: d.getUTCHours(),
    dow: d.getUTCDay(),
    minute: d.getUTCMinutes(),
    lang: (extra.lang || (typeof navigator !== 'undefined' && navigator.language) || 'en').slice(0, 2),
    user: extra.user || null,
    visitor: extra.visitor || null,
    ...extra,
  };
}

// ═══════════ דלתות 1–18: ליבות טהורות (Policy CDN / Receipts / Fragments / …) ═══════════

/** Door 1: ערכת-מדיניות מוכנה לפרויקט (DSL) */
const POLICY_KIT = {
  pricing: [
    'policy pricing',
    'in n = 10',
    'base = formula x*12 when x>0 where x<-n',
    'promo = formula x*10 when x>20 where x<-n',
    'out = coalesce promo, base',
    'output out',
  ].join('\n'),
  access: [
    'policy access',
    'in __authed = 0',
    'guest = formula 1 when a==0 where a<-__authed',
    'member = formula 99 when a==1 where a<-__authed',
    'out = coalesce member, guest',
    'output out',
  ].join('\n'),
  ranking: [
    'policy ranking',
    'in score = 50',
    'hi = formula 1 when s>=70 where s<-score',
    'mid = formula 2 when s>=40 where s<-score',
    'lo = formula 3 when s<40 where s<-score',
    'out = coalesce hi, mid, lo',
    'output out',
  ].join('\n'),
};

/** Door 7: נתב מדיניות — בוחר איזו מדיניות להריץ לפי מפתח */
function metaRoute(selectKey, map, fetchSync) {
  const name = map[String(selectKey)] || map.default || map['*'];
  if (!name) throw new Error('metaRoute: אין מיפוי ל-' + selectKey);
  const pol = fetchSync(name);
  if (!pol?.dag) throw new Error('metaRoute: מדיניות חסרה ' + name);
  return { name, dag: pol.dag };
}

/** Door 9/context: כריכת-הקשר (Σ2-style AAD) — תוצאה לשעה X לא עוברת לשעה Y */
function contextBind(output, ctx) {
  const ctxCanon = xiCanon(ctx || {});
  const ctxDigest = hex(sha256(Buffer.from('sc-ctx:v1:' + ctxCanon))).slice(0, 32);
  const sealed = hex(sha256(Buffer.from('sc-ctxseal:' + ctxDigest + ':' + xiCanon(output)))).slice(0, 32);
  return { output, ctxDigest, sealed, ctxCanon };
}
function contextCheck(bound, ctx) {
  if (!bound || bound.output === undefined) return false;
  const again = contextBind(bound.output, ctx);
  return again.ctxDigest === bound.ctxDigest && again.sealed === bound.sealed;
}
/** Door 9: AEAD אמיתי — מצפין מטען עם AAD=ctxDigest; פענוח עם הקשר אחר נכשל */
function contextSealPayload(plainBuf, ctx) {
  const { ctxDigest } = contextBind(0, ctx || {});
  return { ct: encrypt(Buffer.isBuffer(plainBuf) ? plainBuf : Buffer.from(String(plainBuf)), 'livectx:' + ctxDigest), ctxDigest };
}
function contextOpenPayload(ct, ctx) {
  const { ctxDigest } = contextBind(0, ctx || {});
  return decrypt(ct, 'livectx:' + ctxDigest);
}

/** Door 13/16: קבלת-הוכחה ניידת (ללא מפתח — מזהה תוכן; חתימה נוספת ב-Cloud) */
function makeReceiptBody(dagId, inputs, output, meta = {}) {
  const inputsCanon = xiCanon(inputs || {});
  const outputDigest = xiDigest(output);
  const body = { v: 1, dagId, inputsCanon, outputDigest, ts: meta.ts || Date.now(), visitor: meta.visitor || null };
  body.id = hex(sha256(Buffer.from('screceipt:v1:' + xiCanon(body)))).slice(0, 40);
  return body;
}
function verifyReceiptBody(body, output) {
  if (!body || !body.id) return false;
  if (output !== undefined && xiDigest(output) !== body.outputDigest) return false;
  const { id, ...rest } = body;
  const calc = hex(sha256(Buffer.from('screceipt:v1:' + xiCanon(rest)))).slice(0, 40);
  return calc === id;
}

/** Door 13: עקבות-ענפים לקונצנזוס-חישוב */
function branchTrace(evalResult) {
  const skipped = evalResult.skipped || [];
  const order = evalResult.order || Object.keys(evalResult.results || {});
  const taken = order.filter((id) => !skipped.includes(id));
  const pathDigest = hex(sha256(Buffer.from('scpath:' + xiCanon({ taken, skipped })))).slice(0, 32);
  return { taken, skipped, pathDigest };
}
/**
 * קונצנזוס על עץ-החלטות: אם ≥k מסכימים על pathDigest — ok;
 * אם שני pathDigest שונים עם תמיכה — logic_fork.
 */
function consensusBranches(reports, k = 2) {
  const by = new Map();
  for (const r of reports || []) {
    if (!r?.pathDigest) continue;
    if (!by.has(r.pathDigest)) by.set(r.pathDigest, { pathDigest: r.pathDigest, authors: new Set(), sample: r });
    by.get(r.pathDigest).authors.add(r.author || Math.random());
  }
  const ranked = [...by.values()].map((g) => ({ ...g, agree: g.authors.size })).sort((a, b) => b.agree - a.agree);
  if (!ranked.length) return { ok: false, fork: false, agree: 0 };
  const top = ranked[0];
  const rival = ranked[1];
  const fork = !!(rival && rival.agree >= 1 && top.agree >= 1 && rival.pathDigest !== top.pathDigest);
  return { ok: top.agree >= k, fork, agree: top.agree, pathDigest: top.pathDigest, taken: top.sample?.taken, need: k };
}

/** Door 14: פיצול DAG לשברים טהורים + הרכבת digests (Merkle-ish) */
function xiFragment(dag, parts = 2) {
  const order = xiDagPlan(dag);
  parts = Math.max(1, Math.min(parts, order.length));
  const size = Math.ceil(order.length / parts);
  const frags = [];
  for (let i = 0; i < parts; i++) {
    const ids = order.slice(i * size, (i + 1) * size);
    if (!ids.length) continue;
    const nodes = {};
    for (const id of ids) nodes[id] = dag.nodes[id];
    // גבולות: תלויות מחוץ לשבר → value placeholders (יימסרו בהרכבה)
    for (const id of ids) {
      for (const dep of xiNodeRefs(dag.nodes[id])) {
        if (!nodes[dep] && dag.nodes[dep]) nodes[dep] = { value: { $boundary: dep } };
      }
    }
    frags.push({ i, nodes, output: ids[ids.length - 1], ids });
  }
  return frags;
}
function xiAssembleDigests(digests) {
  const canon = xiCanon(digests);
  return hex(sha256(Buffer.from('scfrag:v1:' + canon))).slice(0, 32);
}

/** Door 18: diff של כוונה (DAG) — מה נוסף/הוסר/השתנה */
function dagDiff(a, b) {
  const an = a?.nodes || {}, bn = b?.nodes || {};
  const added = [], removed = [], changed = [];
  for (const id of Object.keys(bn)) {
    if (!(id in an)) added.push(id);
    else if (xiCanon(an[id]) !== xiCanon(bn[id])) changed.push(id);
  }
  for (const id of Object.keys(an)) if (!(id in bn)) removed.push(id);
  const outChanged = (a?.output || null) !== (b?.output || null);
  return { added, removed, changed, outChanged, empty: !added.length && !removed.length && !changed.length && !outChanged };
}
function dagApplyDiff(base, diff, newerNodes) {
  const nodes = { ...(base.nodes || {}) };
  for (const id of diff.removed || []) delete nodes[id];
  for (const id of [...(diff.added || []), ...(diff.changed || [])]) {
    if (newerNodes[id] !== undefined) nodes[id] = newerNodes[id];
  }
  return { nodes, output: newerNodes.__output !== undefined ? newerNodes.__output : base.output };
}

/** Door 15: מיפוי תרחיש → מדיניות/UI (אפליקציה מודעת-תרחיש) */
function scenarioAppSelect(scenario, map) {
  const s = typeof scenario === 'string' ? scenario : scenario?.scenario;
  return (map && (map[s] || map.default || map.healthy_steady)) || null;
}

// ═══════════ SkyLattice-ΔB / SkyDB: Write-Intent Lattice (נתיב-חם מקומי) ═══════════
// לא ORM. כל כתיבה = רשומת-כוונה משורשרת (seq+prevDigest). הנתיב החם הוא WAL
// מקומי (SQLite/memory/…); הרשת מקבלת checkpoints + מקטעי-WAL — לא כל INSERT.
// תפוקת "מיליונים" = סכום shards על עובדי-Node, לא throughput של ממסרים.

function skydbDigest(rec) {
  return hex(sha256(Buffer.from('skydb:v1:' + xiCanon({
    shard: rec.shard, key: rec.key, op: rec.op, payloadCanon: rec.payloadCanon,
    hlc: rec.hlc, prevDigest: rec.prevDigest || null, author: rec.author || null, seq: rec.seq | 0,
  })))).slice(0, 32);
}

/** S9: Rendezvous shard — מפתח → shard + בעלים דטרמיניסטי מבין peers */
function skydbShardOf(key, peers = [], nShards = SKYDB_SHARDS) {
  nShards = Math.max(1, nShards | 0);
  const idx = Number(BigInt('0x' + hex(sha256(Buffer.from('skydb:shard:' + String(key)))).slice(0, 8)) % BigInt(nShards));
  const shard = 's' + idx;
  if (!peers.length) return { shard, owner: null, idx, nShards };
  let best = peers[0], bestScore = -1n;
  for (const p of peers) {
    const score = BigInt('0x' + hex(sha256(Buffer.from(String(p) + ':skydb:' + idx))).slice(0, 16));
    if (score > bestScore) { bestScore = score; best = p; }
  }
  return { shard, owner: best, idx, nShards };
}

/** S12: סכמה כ-DAG כוונה — diff בין גרסאות סכמה */
function skydbSchemaDiff(prev, next) {
  const toDag = (s) => ({
    nodes: Object.fromEntries(Object.entries(s || {}).map(([k, v]) => [k, { value: v }])),
    output: Object.keys(s || {})[0] || null,
  });
  return dagDiff(toDag(prev), toDag(next));
}

/** S13: נעיצת tip אחרי fork — בוחר seq אחרון שלפני הפיצול */
function skydbPinOnFork(history, forkSeq) {
  const list = (history || []).filter((h) => (h.seq | 0) < (forkSeq | 0)).sort((a, b) => b.seq - a.seq);
  return list[0] || null;
}

/**
 * SkyDB — מנוע Write-Intent Lattice.
 * engines: memory | sqlite | postgres* | redis*  (*hooks — דורשים מודול חיצוני)
 */
class SkyDB {
  constructor(opts = {}) {
    this.dir = opts.dir || path.join(ROOT, '.cloud-skydb');
    this.engine = opts.engine || SKYDB_ENGINE;
    this.nShards = opts.nShards || SKYDB_SHARDS;
    this.peers = opts.peers || [];
    this.author = opts.author || 'local';
    this.cloud = opts.cloud || null;
    this._tips = new Map();       // shard -> { seq, digest, hlc }
    this._history = new Map();    // shard -> [{seq,digest,hlc}]
    this._ops = 0;
    this._opsWindow = [];
    this._pending = [];           // records awaiting segment flush
    this._lastCheckpoint = 0;
    this._pinned = null;
    this._adapter = null;
    this._policyGate = opts.policyGate || null; // async (ns,key,value)=>allow
    this.configure({ engine: this.engine, path: opts.path, url: opts.url });
  }

  configure(cfg = {}) {
    if (cfg.engine) this.engine = cfg.engine;
    if (cfg.nShards) this.nShards = cfg.nShards;
    if (cfg.peers) this.peers = cfg.peers;
    if (cfg.author) this.author = cfg.author;
    if (cfg.policyGate) this._policyGate = cfg.policyGate;
    try { fs.mkdirSync(this.dir, { recursive: true }); } catch {}
    if (this.engine === 'memory') this._adapter = this._memAdapter();
    else if (this.engine === 'sqlite') this._adapter = this._sqliteAdapter(cfg.path || path.join(this.dir, 'hot.sqlite'));
    else if (this.engine === 'postgres') this._adapter = this._remoteHook('postgres', cfg.url || process.env.DATABASE_URL);
    else if (this.engine === 'redis') this._adapter = this._remoteHook('redis', cfg.url || process.env.REDIS_URL);
    else throw new Error('SkyDB: engine לא נתמך ' + this.engine);
    return this;
  }

  _memAdapter() {
    const kv = new Map();
    return {
      kind: 'memory',
      put(ns, key, row) { kv.set(ns + '\0' + key, row); },
      get(ns, key) { return kv.get(ns + '\0' + key) ?? null; },
      del(ns, key) { kv.delete(ns + '\0' + key); },
      query(ns, prefix = '') {
        const out = [];
        for (const [k, v] of kv) {
          if (!k.startsWith(ns + '\0')) continue;
          const key = k.slice(ns.length + 1);
          if (!prefix || key.startsWith(prefix)) out.push({ key, ...v });
        }
        return out;
      },
      close() { kv.clear(); },
    };
  }

  _sqliteAdapter(dbPath) {
    let DatabaseSync;
    try { ({ DatabaseSync } = require('node:sqlite')); }
    catch { return this._fileWalAdapter(dbPath.replace(/\.sqlite$/, '') + '.wal'); }
    const db = new DatabaseSync(dbPath);
    db.exec(`PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;
      CREATE TABLE IF NOT EXISTS skydb(
        ns TEXT NOT NULL, k TEXT NOT NULL, v TEXT, meta TEXT, PRIMARY KEY(ns,k)
      );`);
    const ins = db.prepare('INSERT INTO skydb(ns,k,v,meta) VALUES (?,?,?,?) ON CONFLICT(ns,k) DO UPDATE SET v=excluded.v, meta=excluded.meta');
    const sel = db.prepare('SELECT v, meta FROM skydb WHERE ns=? AND k=?');
    const del = db.prepare('DELETE FROM skydb WHERE ns=? AND k=?');
    const q = db.prepare('SELECT k, v, meta FROM skydb WHERE ns=? AND k LIKE ?');
    return {
      kind: 'sqlite',
      put(ns, key, row) { ins.run(ns, key, JSON.stringify(row.value), JSON.stringify({ digest: row.digest, seq: row.seq, hlc: row.hlc, sealed: row.sealed || null })); },
      get(ns, key) {
        const r = sel.get(ns, key); if (!r) return null;
        let value, meta = {};
        try { value = JSON.parse(r.v); } catch { value = r.v; }
        try { meta = JSON.parse(r.meta || '{}'); } catch {}
        return { value, ...meta };
      },
      del(ns, key) { del.run(ns, key); },
      query(ns, prefix = '') {
        const rows = q.all(ns, (prefix || '') + '%');
        return rows.map((r) => {
          let value, meta = {};
          try { value = JSON.parse(r.v); } catch { value = r.v; }
          try { meta = JSON.parse(r.meta || '{}'); } catch {}
          return { key: r.k, value, ...meta };
        });
      },
      close() { try { db.close(); } catch {} },
    };
  }

  /** נפילה בטוחה בלי node:sqlite — קובץ JSONL + אינדקס */
  _fileWalAdapter(base) {
    const idxPath = base + '.idx.json';
    const walPath = base + '.jsonl';
    let idx = {};
    try { idx = JSON.parse(fs.readFileSync(idxPath, 'utf8')); } catch {}
    const flushIdx = () => { try { fs.writeFileSync(idxPath, JSON.stringify(idx)); } catch {} };
    return {
      kind: 'filewal',
      put(ns, key, row) {
        const id = ns + '\0' + key;
        idx[id] = row;
        try { fs.appendFileSync(walPath, JSON.stringify({ ns, key, row, ts: Date.now() }) + '\n'); } catch {}
        flushIdx();
      },
      get(ns, key) { return idx[ns + '\0' + key] || null; },
      del(ns, key) { delete idx[ns + '\0' + key]; flushIdx(); },
      query(ns, prefix = '') {
        const out = [];
        for (const [id, row] of Object.entries(idx)) {
          if (!id.startsWith(ns + '\0')) continue;
          const key = id.slice(ns.length + 1);
          if (!prefix || key.startsWith(prefix)) out.push({ key, ...row });
        }
        return out;
      },
      close() { flushIdx(); },
    };
  }

  _remoteHook(kind, url) {
    if (!url) {
      log(`SkyDB: ${kind} נבחר אך אין URL — נופל ל-sqlite. הגדר DATABASE_URL/REDIS_URL.`);
      return this._sqliteAdapter(path.join(this.dir, 'hot.sqlite'));
    }
    // hooks בלי תלות חובה: מנסים require דינמי; אחרת sqlite
    try {
      if (kind === 'postgres') {
        const mod = require('pg'); // אופציונלי
        throw new Error('התקן מודול pg וחבר מתאם מלא — כרגע fallback sqlite עם סימון engine=postgres-pending');
      }
      if (kind === 'redis') {
        require('redis');
        throw new Error('התקן מודול redis — כרגע fallback sqlite');
      }
    } catch (err) {
      log(`SkyDB: מתאם ${kind} לא זמין (${err.message}) — sqlite.`);
    }
    const a = this._sqliteAdapter(path.join(this.dir, 'hot-' + kind + '.sqlite'));
    a.kind = kind + '+sqlite-fallback';
    a.remoteUrl = url;
    return a;
  }

  shardOf(key) { return skydbShardOf(key, this.peers, this.nShards); }

  _tip(shard) { return this._tips.get(shard) || { seq: -1, digest: null, hlc: 0 }; }

  _track(shard, rec) {
    this._tips.set(shard, { seq: rec.seq, digest: rec.digest, hlc: rec.hlc });
    const h = this._history.get(shard) || [];
    h.push({ seq: rec.seq, digest: rec.digest, hlc: rec.hlc });
    if (h.length > 256) h.shift();
    this._history.set(shard, h);
    this._ops++;
    const now = Date.now();
    this._opsWindow.push(now);
    while (this._opsWindow.length && now - this._opsWindow[0] > 1000) this._opsWindow.shift();
  }

  async _gate(ns, key, value, opts) {
    // S11: receipt-gated writes
    if (opts.requireReceipt || opts.receipt) {
      const signed = opts.receipt;
      if (!signed || !verifyReceiptBody(signed, opts.receiptOutput !== undefined ? opts.receiptOutput : value)) {
        throw new Error('SkyDB S11: קבלה חסרה/לא תקפה — הכתיבה נדחתה');
      }
      if (signed.event && this.cloud?.verifyReceipt) {
        const v = this.cloud.verifyReceipt(signed, opts.receiptOutput !== undefined ? opts.receiptOutput : value);
        if (!v.ok) throw new Error('SkyDB S11: חתימת-קבלה נדחתה');
      }
    }
    // S15: policy-as-constraint
    if (opts.policy || this._policyGate) {
      const gate = opts.policyGate || this._policyGate;
      if (gate) {
        const ok = await gate(ns, key, value, opts);
        if (!ok) throw new Error('SkyDB S15: מדיניות דחתה את הכתיבה');
      } else if (opts.policy && this.cloud?.resolveSite) {
        const out = await this.cloud.resolveSite(opts.policy, { inputs: { ...(opts.policyInputs || {}), __key: key, __ns: ns }, waitMs: opts.waitMs || 2000 });
        if (out.output === 0 || out.output === false || out.output === 'deny') throw new Error('SkyDB S15: resolveSite דחה');
      }
    }
  }

  async put(ns, key, value, opts = {}) {
    await this._gate(ns, key, value, opts);
    const { shard } = this.shardOf(key);
    const tip = this._tip(shard);
    const payloadCanon = xiCanon(value);
    const rec = {
      shard, key: String(key), op: 'put', payloadCanon, hlc: hlcNow(),
      prevDigest: tip.digest, author: this.author, seq: tip.seq + 1,
    };
    rec.digest = skydbDigest(rec);
    this._adapter.put(String(ns), String(key), { value, digest: rec.digest, seq: rec.seq, hlc: rec.hlc, sealed: null });
    this._track(shard, rec);
    this._pending.push(rec);
    return { ok: true, shard, seq: rec.seq, digest: rec.digest, hlc: rec.hlc };
  }

  /** S10: שורה כבולת-הקשר — ערך מוצפן ב-Σ2 AAD של liveContext */
  async sealedPut(ns, key, value, ctx, opts = {}) {
    await this._gate(ns, key, value, opts);
    const bound = contextBind(value, liveContext(ctx || {}));
    let sealedPayload = bound;
      try {
      const sealed = contextSealPayload(Buffer.from(JSON.stringify(value), 'utf8'), liveContext(ctx || {}));
      sealedPayload = { ...bound, ct: sealed.ct };
    } catch { /* בלי מפתחות — נשאר bind בלבד */ }
    const { shard } = this.shardOf(key);
    const tip = this._tip(shard);
    const rec = {
      shard, key: String(key), op: 'sealedPut', payloadCanon: xiCanon({ sealed: true, ctxDigest: bound.ctxDigest }),
      hlc: hlcNow(), prevDigest: tip.digest, author: this.author, seq: tip.seq + 1,
    };
    rec.digest = skydbDigest(rec);
    this._adapter.put(String(ns), String(key), { value: sealedPayload, digest: rec.digest, seq: rec.seq, hlc: rec.hlc, sealed: bound.ctxDigest });
    this._track(shard, rec);
    this._pending.push(rec);
    return { ok: true, shard, seq: rec.seq, digest: rec.digest, ctxDigest: bound.ctxDigest };
  }

  sealedGet(ns, key, ctx) {
    const row = this._adapter.get(String(ns), String(key));
    if (!row) return null;
    if (!row.sealed && !row.value?.ctxDigest) return row.value;
    const ctxLive = liveContext(ctx || {});
    if (row.value?.ct) {
      try {
        const buf = contextOpenPayload(row.value.ct, ctxLive);
        return JSON.parse(buf.toString('utf8'));
      } catch {
        // נסיון כ-canon string
        try { return JSON.parse(contextOpenPayload(row.value.ct, ctxLive).toString()); }
        catch { throw new Error('SkyDB S10: הקשר לא תואם — פענוח נכשל'); }
      }
    }
    if (!contextCheck(row.value, ctxLive)) throw new Error('SkyDB S10: contextCheck נכשל');
    return row.value.output !== undefined ? row.value.output : row.value;
  }

  get(ns, key) {
    const row = this._adapter.get(String(ns), String(key));
    return row ? row.value : undefined;
  }

  del(ns, key, opts = {}) {
    const { shard } = this.shardOf(key);
    const tip = this._tip(shard);
    const rec = {
      shard, key: String(key), op: 'del', payloadCanon: 'null', hlc: hlcNow(),
      prevDigest: tip.digest, author: this.author, seq: tip.seq + 1,
    };
    rec.digest = skydbDigest(rec);
    this._adapter.del(String(ns), String(key));
    this._track(shard, rec);
    this._pending.push(rec);
    return { ok: true, shard, seq: rec.seq, digest: rec.digest };
  }

  async batch(ops, opts = {}) {
    const out = [];
    for (const op of ops || []) {
      if (op.op === 'del') out.push(this.del(op.ns, op.key, opts));
      else if (op.op === 'sealedPut') out.push(await this.sealedPut(op.ns, op.key, op.value, op.ctx, { ...opts, ...op }));
      else out.push(await this.put(op.ns, op.key, op.value, { ...opts, ...op }));
    }
    return out;
  }

  query(ns, prefix = '') { return this._adapter.query(String(ns), prefix); }

  verifyChain(shard) {
    const h = this._history.get(shard) || [];
    for (let i = 1; i < h.length; i++) {
      if (h[i].seq !== h[i - 1].seq + 1) return { ok: false, reason: 'seq-gap', at: h[i].seq };
    }
    return { ok: true, tip: this._tip(shard), len: h.length };
  }

  /** מחזיר checkpoint לפרסום לרשת (Cloud מפרסם) */
  checkpoint() {
    const shards = {};
    for (const [shard, tip] of this._tips) shards[shard] = tip;
    const pending = this._pending.splice(0, this._pending.length);
    const body = { v: 1, shards, nPending: pending.length, ops: this._ops, ts: Date.now(), pinned: this._pinned };
    body.digest = hex(sha256(Buffer.from('skydb:chk:' + xiCanon(shards)))).slice(0, 32);
    this._lastCheckpoint = Date.now();
    return { body, pending };
  }

  pinCheckpoint(seqOrTip) {
    this._pinned = typeof seqOrTip === 'object' ? seqOrTip : { seq: seqOrTip | 0 };
    return this._pinned;
  }

  health() {
    const lag = this._lastCheckpoint ? Date.now() - this._lastCheckpoint : 0;
    return {
      engine: this._adapter?.kind || this.engine,
      ops: this._ops,
      opsPerSec: this._opsWindow.length,
      shards: this._tips.size,
      nShards: this.nShards,
      lagMs: lag,
      pending: this._pending.length,
      pinned: this._pinned,
      tips: Object.fromEntries(this._tips),
    };
  }

  close() { try { this._adapter?.close?.(); } catch {} }
}

// ═══════════ SkyLattice-Roam: שכבת-שמות נודדת (Mutable Roaming Naming) ═══════════
// כתובת קריאה ויציבה שמצביעה על התוכן ה*עדכני* (hash/URL/jobId/dagId), בעוד
// התוכן עצמו "נע במרחב" — השכפול שלו מסתובב על-פני ממסרים/שרתי-אחסון בריאים.
// המצביע משורשר-גיבוב (כמו Σ1) עם seq מונוטוני, כך שנדידה לא מאפשרת "החזרה
// לאחור" (rollback). זה IPNS-כמו + שכפול-נודד; התוכן תמיד חי ברשת (אפס-מפעיל),
// והשם תמיד מתפייס תחת ה-SYNC_ID. גבול כן: "נדידה" = שכפול מסתובב + מצביע
// משתנה — לא אתר שמרחף בלי אחסון. חייבים ממסרים/אחסון היכנשהו.

/** רשומת-שם: הצבעה חתומה-בעתיד עם seq מונוטוני ו-prev (שרשרת-גיבוב) */
function roamRecord(name, target, seq, prev, ts) {
  return { name: String(name), target, seq: seq | 0, prev: prev || null, ts: ts | 0 };
}
/** גיבוב-רשומה — מזהה בשרשרת; שינוי כלשהו משנה את ה-hash */
function roamHash(rec) { return hex(sha256(Buffer.from('skyroam:v1:' + xiCanon({ name: rec.name, target: rec.target, seq: rec.seq, prev: rec.prev, ts: rec.ts })))).slice(0, 32); }

/**
 * RoamResolver — קולט רשומות-שם (אולי מכמה ממסרים/עותקים) ומכריע מה ה"עכשווי":
 * seq גבוה מנצח; שווה-seq → תיקו דטרמיניסטי לפי hash גדול יותר; seq נמוך מהידוע
 * נדחה (הגנת-rollback). שומר היסטוריה ומזהה נתק-שרשרת (prev לא תואם) לשקיפות.
 */
class RoamResolver {
  constructor() { this.current = null; this.history = []; this.bySeq = new Map(); this.gaps = 0; }
  ingest(rec) {
    if (!rec || typeof rec.name !== 'string' || rec.target === undefined || !(rec.seq >= 0)) return false;
    const hash = roamHash(rec);
    const full = { ...rec, hash };
    // Door 8 time-travel: תמיד נשמר ב-bySeq; current מתקדם רק קדימה (הגנת-rollback ל-tip)
    const prevAt = this.bySeq.get(rec.seq);
    if (!prevAt || hash > prevAt.hash) this.bySeq.set(rec.seq, full);
    if (!this.current) { this.current = full; this.history.push(hash); return true; }
    if (rec.seq < this.current.seq) return false;                       // tip לא חוזר לאחור
    if (rec.seq === this.current.seq) { if (hash <= this.current.hash) return false; }
    else if (rec.seq === this.current.seq + 1 && rec.prev !== this.current.hash) this.gaps++;
    this.current = full; this.history.push(hash);
    return true;
  }
  resolve() { return this.current ? this.current.target : undefined; }
  /** Door 8: פתירה לגרסה היסטורית לפי seq (audit / time-travel יזום) */
  atSeq(seq) { const r = this.bySeq.get(seq | 0); return r ? r.target : undefined; }
  recordAt(seq) { return this.bySeq.get(seq | 0) || null; }
}

/**
 * roamRelaySubset — בוחר תת-קבוצת ממסרים "לנדידה" של השכפול: מעדיף בריאים
 * (משוקלל-ציון), אך *מסתובב* לפי epoch כדי שהמיקום ינוע במרחב הרשת עם הזמן.
 * דטרמיניסטי בהינתן הקלט — ניתן-לבדיקה.
 */
function roamRelaySubset(scored, k, epoch = 0) {
  const sorted = [...scored].filter((s) => (s.score ?? 0) > 0).sort((a, b) => (b.score - a.score) || (a.id < b.id ? -1 : 1));
  if (!sorted.length) return [];
  k = Math.max(1, Math.min(k, sorted.length));
  const start = ((epoch % sorted.length) + sorted.length) % sorted.length;
  const out = [];
  for (let i = 0; i < k; i++) out.push(sorted[(start + i) % sorted.length].id);
  return out;
}

// ═══════════════ SkyLattice-Φ: מנוע-הזרימה בזמן-אמת ("תדרים") ═══════════════
// שכבת dataflow ריאקטיבית שמאחדת ארבעה פרדיגמות-קוד לזרימה אחת: (1) זרמים
// ריאקטיביים (map/filter/merge/throttle), (2) גרף-dataflow, (3) ערוצי-CSP
// (Channel: send/recv), (4) מדיניות-נוסחה (FormulaVM). ה"תדרים" הם קצב-דגימה:
// כל מקור פועם בתדר (Hz) עם מספר-epoch. הסינכרון מובטח ע"י מחסום-epoch
// (zipComplete): טאפל נפלט *רק* כשכל הכניסות סיפקו ערך לאותו epoch — כך
// מובטחות "תוצאות שלמות בזמן-אמת". הליבה דטרמיניסטית (שעון-וירטואלי לבדיקה).

/** שעון: מקור-הזמן/התדר. tick() ידני = דטרמיניסטי; start(hz) = זמן-אמת (unref) */
class Clock {
  constructor() { this.epoch = 0; this.subs = []; this.timer = null; }
  onTick(fn) { this.subs.push(fn); return () => { const i = this.subs.indexOf(fn); if (i >= 0) this.subs.splice(i, 1); }; }
  tick() { const e = ++this.epoch; for (const fn of [...this.subs]) fn(e); return e; }
  start(hz = 1) { const ms = Math.max(1, Math.round(1000 / hz)); this.timer = setInterval(() => this.tick(), ms); this.timer.unref?.(); return this; }
  stop() { if (this.timer) clearInterval(this.timer); this.timer = null; }
}

/** זרם ריאקטיבי: push-based, נושא (value, epoch). קומבינטורים משורשרים. */
class Stream {
  constructor() { this.subs = []; }
  subscribe(fn) { this.subs.push(fn); return () => { const i = this.subs.indexOf(fn); if (i >= 0) this.subs.splice(i, 1); }; }
  push(value, epoch) { for (const s of [...this.subs]) { try { s(value, epoch); } catch { /* מנוי בודד שנכשל לא מפיל את הזרם */ } } }
  map(f) { const o = new Stream(); this.subscribe((v, e) => o.push(f(v, e), e)); return o; }
  filter(p) { const o = new Stream(); this.subscribe((v, e) => { if (p(v, e)) o.push(v, e); }); return o; }
  scan(f, seed) { let acc = seed; const o = new Stream(); this.subscribe((v, e) => { acc = f(acc, v, e); o.push(acc, e); }); return o; }
  /** throttle לפי מספר-epoch: פולט 1 מכל n פעימות — הורדת-תדר (decimation) */
  throttle(n) { const o = new Stream(); this.subscribe((v, e) => { if (e % n === 0) o.push(v, e); }); return o; }
  /** נוסחה כטרנספורמציה: מעריך ביטוי FormulaVM עם x=הערך הנכנס */
  formula(expr, vm = new FormulaVM()) { const o = new Stream(); this.subscribe((v, e) => o.push(vm.eval(expr, { x: Number(v), epoch: e }), e)); return o; }
  static of(clock, valueFn) { const o = new Stream(); clock.onTick((e) => o.push(valueFn(e), e)); return o; }
  static merge(streams) { const o = new Stream(); streams.forEach((s, i) => s.subscribe((v, e) => o.push({ lane: i, value: v }, e))); return o; }
  /**
   * מחסום-סינכרון (barrier): פולט מערך-ערכים באורך streams.length רק כאשר *כל*
   * הכניסות סיפקו ערך לאותו epoch. epoch-ים חלקיים לא נפלטים (תוצאות שלמות בלבד).
   * חלון זיכרון חסום (windowW) מונע דליפה מ-epoch-ים ישנים שלא הושלמו.
   */
  static zipComplete(streams, windowW = 256) {
    const o = new Stream();
    const buffers = new Map(); // epoch -> Map(laneIndex -> value)
    streams.forEach((s, i) => s.subscribe((v, e) => {
      if (!buffers.has(e)) buffers.set(e, new Map());
      const b = buffers.get(e); b.set(i, v);
      if (b.size === streams.length) { const tuple = streams.map((_, j) => b.get(j)); buffers.delete(e); o.push(tuple, e); }
      if (buffers.size > windowW) { const oldest = Math.min(...buffers.keys()); buffers.delete(oldest); } // גיזום חסום-זיכרון
    }));
    return o;
  }
}

/** ערוץ CSP: תור אסינכרוני עם send/recv/close — תיאום זרימות בין יצרנים-צרכנים */
class Channel {
  constructor(cap = Infinity) { this.cap = cap; this.q = []; this.recvW = []; this.closed = false; }
  send(v) {
    if (this.closed) throw new Error('Φ Channel: send על ערוץ סגור');
    if (this.recvW.length) { this.recvW.shift()({ value: v, done: false }); return true; }
    if (this.q.length >= this.cap) return false; // מלא (backpressure) — היצרן יאט
    this.q.push(v); return true;
  }
  recv() {
    if (this.q.length) return Promise.resolve({ value: this.q.shift(), done: false });
    if (this.closed) return Promise.resolve({ value: undefined, done: true });
    return new Promise((res) => this.recvW.push(res));
  }
  close() { this.closed = true; while (this.recvW.length) this.recvW.shift()({ value: undefined, done: true }); }
}

// ─────────────────── secp256k1 + Schnorr (BIP-340), ללא תלות ───────────────────

const P = 2n ** 256n - 2n ** 32n - 977n;
const N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141n;
const G = {
  x: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798n,
  y: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8n,
};

const mod = (a, m = P) => ((a % m) + m) % m;
const powmod = (b, e, m) => {
  let r = 1n; b = mod(b, m);
  while (e > 0n) { if (e & 1n) r = (r * b) % m; b = (b * b) % m; e >>= 1n; }
  return r;
};
const inv = (a, m = P) => powmod(mod(a, m), m - 2n, m);

function ptDouble(a) {
  if (!a) return null;
  const lam = mod(3n * a.x * a.x * inv(2n * a.y));
  const x = mod(lam * lam - 2n * a.x);
  return { x, y: mod(lam * (a.x - x) - a.y) };
}
function ptAdd(a, b) {
  if (!a) return b;
  if (!b) return a;
  if (a.x === b.x) return a.y === b.y ? ptDouble(a) : null;
  const lam = mod((b.y - a.y) * inv(b.x - a.x));
  const x = mod(lam * lam - a.x - b.x);
  return { x, y: mod(lam * (a.x - x) - a.y) };
}
function ptMul(pt, k) {
  let r = null, a = pt;
  while (k > 0n) { if (k & 1n) r = ptAdd(r, a); a = ptDouble(a); k >>= 1n; }
  return r;
}

const big = (buf) => BigInt('0x' + hex(buf));
const b32 = (n) => unhex(n.toString(16).padStart(64, '0'));
const taggedHash = (tag, ...data) => {
  const th = sha256(Buffer.from(tag));
  return sha256(th, th, ...data);
};
const xorBuf = (a, b) => Buffer.from(a.map((v, i) => v ^ b[i]));

/** חתימת Schnorr לפי BIP-340 — זה מה שהרשת דורשת ומאמתת */
function schnorrSign(msg32, privBig) {
  let d = privBig;
  const Pub = ptMul(G, d);
  if (Pub.y % 2n !== 0n) d = N - d;
  const px = b32(Pub.x);
  const t = xorBuf(b32(d), taggedHash('BIP0340/aux', crypto.randomBytes(32)));
  const k0 = mod(big(taggedHash('BIP0340/nonce', t, px, msg32)), N);
  if (k0 === 0n) throw new Error('bad nonce');
  const R = ptMul(G, k0);
  const k = R.y % 2n === 0n ? k0 : N - k0;
  const rx = b32(R.x);
  const e = mod(big(taggedHash('BIP0340/challenge', rx, px, msg32)), N);
  return Buffer.concat([rx, b32(mod(k + e * d, N))]);
}

// ─────────── SkyLattice-Σ3: אימות Schnorr מלא — אפס אמון בממסרים ───────────
// כל אירוע שמגיע מהרשת מאומת מתמטית (sG = R + eP לפי BIP-340) לפני קבלה.
// ממסר זדוני לא יכול לזייף, לשנות או להחדיר נתונים בשם אף מחבר.

/** שחזור נקודה על העקומה מקואורדינטת X (עם Y זוגי) — BIP-340 lift_x */
function liftX(x) {
  if (x >= P) return null;
  const c = mod(x * x * x + 7n);
  const y = powmod(c, (P + 1n) / 4n, P);
  if ((y * y) % P !== c) return null;
  return { x, y: y % 2n === 0n ? y : P - y };
}

/** אימות חתימת Schnorr (BIP-340) */
function schnorrVerify(sig, msg32, pubHex) {
  if (sig.length !== 64) return false;
  const r = big(sig.subarray(0, 32));
  const s = big(sig.subarray(32));
  if (r >= P || s >= N) return false;
  const Ppt = liftX(big(unhex(pubHex)));
  if (!Ppt) return false;
  const e = mod(big(taggedHash('BIP0340/challenge', sig.subarray(0, 32), unhex(pubHex), msg32)), N);
  const R = ptAdd(ptMul(G, s), ptMul(Ppt, N - e));
  return !!R && R.y % 2n === 0n && R.x === r;
}

/** אימות אירוע שלם: תקינות ה-ID (hash) + החתימה. עם מטמון נגד אימות כפול. */
const verifiedIds = new Set();
let rejectedEvents = 0;
function verifyEvent(ev) {
  try {
    if (!ev || typeof ev.id !== 'string' || typeof ev.sig !== 'string' || typeof ev.pubkey !== 'string') return false;
    // בדיקת ה-hash רצה תמיד (זולה) — מונעת replay של תוכן זר תחת id מאומת;
    // המטמון חוסך רק את אימות החתימה (היקר) עבור אותו id.
    const calc = hex(sha256(Buffer.from(JSON.stringify([0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content]))));
    if (calc !== ev.id) return false;
    if (verifiedIds.has(ev.id)) return true;
    if (!schnorrVerify(unhex(ev.sig), unhex(ev.id), ev.pubkey)) return false;
    if (verifiedIds.size > 5000) verifiedIds.clear();
    verifiedIds.add(ev.id);
    return true;
  } catch { return false; }
}

// ─────────────────────── bech32 (לכתובת הגלובלית, NIP-19) ───────────────────────

const B32C = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
function bech32Polymod(values) {
  const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
  let chk = 1;
  for (const v of values) {
    const b = chk >> 25;
    chk = ((chk & 0x1ffffff) << 5) ^ v;
    for (let i = 0; i < 5; i++) if ((b >> i) & 1) chk ^= GEN[i];
  }
  return chk;
}
function bech32Encode(hrp, data5) {
  const hrpExp = [...[...hrp].map((c) => c.charCodeAt(0) >> 5), 0, ...[...hrp].map((c) => c.charCodeAt(0) & 31)];
  const pm = bech32Polymod([...hrpExp, ...data5, 0, 0, 0, 0, 0, 0]) ^ 1;
  const chk = Array.from({ length: 6 }, (_, i) => (pm >> (5 * (5 - i))) & 31);
  return hrp + '1' + [...data5, ...chk].map((d) => B32C[d]).join('');
}
function to5bit(bytes) {
  const out = []; let acc = 0, bits = 0;
  for (const b of bytes) {
    acc = (acc << 8) | b; bits += 8;
    while (bits >= 5) { bits -= 5; out.push((acc >> bits) & 31); }
  }
  if (bits > 0) out.push((acc << (5 - bits)) & 31);
  return out;
}
/** יצירת כתובת naddr קבועה לעמוד הסטטוס */
function makeNaddr(dTag, pubkeyHex, kind, relay) {
  const tlv = [];
  const push = (type, buf) => { tlv.push(type, buf.length, ...buf); };
  push(0, Buffer.from(dTag, 'utf8'));
  push(1, Buffer.from(relay, 'ascii'));
  push(2, unhex(pubkeyHex));
  const kb = Buffer.alloc(4); kb.writeUInt32BE(kind); push(3, kb);
  return bech32Encode('naddr', to5bit(Buffer.from(tlv)));
}

// ────────────────────────── זהות והצפנה מתוך ה-SYNC_ID ──────────────────────────

let syncId = SYNC_ID;

/** בהרצה ראשונה: יצירת ID וכתיבתו לתוך הקובץ הזה עצמו */
function ensureId() {
  if (syncId && syncId !== 'AUTO') return;
  syncId = 'SC-' + hex(crypto.randomBytes(20)).toUpperCase();
  try {
    const self = fs.readFileSync(__filename, 'utf8');
    const updated = self.replace(`const SYNC_ID = 'AUTO';`, `const SYNC_ID = '${syncId}';`);
    if (updated === self) throw new Error('SYNC_ID marker not found');
    fs.writeFileSync(__filename, updated, 'utf8');
    log('נוצר SYNC_ID חדש ונשמר בתוך הקובץ:');
    log('  ' + syncId);
    log('כדי לסנכרן ממקום אחר — העתק את cloud.js הזה (עם ה-ID שבפנים) לשם.');
  } catch (err) {
    log('אזהרה: לא ניתן לכתוב את ה-ID לקובץ (' + err.message + '). ה-ID לריצה זו: ' + syncId);
  }
}

let keys = null;
function deriveKeys() {
  const seed = sha256(Buffer.from('skycloud:v1:' + syncId));
  let priv = mod(big(seed), N - 1n) + 1n;
  const pub = ptMul(G, priv);
  keys = {
    priv,
    pubkey: hex(b32(pub.x)),
    encKey: sha256(Buffer.from('skycloud:enc:' + syncId)),
    ns: hex(sha256(Buffer.from('skycloud:ns:' + syncId))).slice(0, 12),
  };
}

// SkyLattice-Σ2: הצפנה כבולת-כתובת (Address-Bound AEAD).
// הכתובת הרשתית של כל פיסת מידע (ה-d-tag שלה) נכרכת קריפטוגרפית לתוך
// אימות ההצפנה (AAD של AES-256-GCM). התוצאה: גם עם ciphertext תקין לחלוטין,
// אי אפשר להזיז/להחליף בלוק בין קבצים, מיקומים או מפתחות — הפענוח ייכשל.
const encrypt = (buf, aad) => {
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', keys.encKey, iv);
  if (aad) c.setAAD(Buffer.from('sc-sigma2:' + aad));
  const enc = Buffer.concat([c.update(buf), c.final()]);
  return Buffer.concat([iv, c.getAuthTag(), enc]).toString('base64');
};
const decrypt = (b64, aad) => {
  const raw = Buffer.from(b64, 'base64');
  const d = crypto.createDecipheriv('aes-256-gcm', keys.encKey, raw.subarray(0, 12));
  if (aad) d.setAAD(Buffer.from('sc-sigma2:' + aad));
  d.setAuthTag(raw.subarray(12, 28));
  return Buffer.concat([d.update(raw.subarray(28)), d.final()]);
};

// ────────────────────────────── אירועי רשת ──────────────────────────────

function signEvent(kind, tags, content) {
  const ev = { pubkey: keys.pubkey, created_at: now(), kind, tags, content };
  ev.id = hex(sha256(Buffer.from(JSON.stringify([0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content]))));
  ev.sig = hex(schnorrSign(unhex(ev.id), keys.priv));
  return ev;
}

// חתימה עם מפתח אחר (למשל מפתח-מכשיר של Ξ) — כדי לקבל *מחברים ייחודיים*
function signEventAs(kind, tags, content, kp) {
  const ev = { pubkey: kp.pubkey, created_at: now(), kind, tags, content };
  ev.id = hex(sha256(Buffer.from(JSON.stringify([0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content]))));
  ev.sig = hex(schnorrSign(unhex(ev.id), kp.priv));
  return ev;
}
// מפתח-מכשיר ייחודי לכל תהליך — נגזר מ-SYNC_ID+DEVICE+RUN_SALT (Ξ: פותר עצמאי)
function deviceKeypair() {
  const seed = sha256(Buffer.from('skyxi:device:' + syncId + ':' + DEVICE + ':' + RUN_SALT));
  let priv = mod(big(seed), N - 1n) + 1n;
  const pub = ptMul(G, priv);
  return { priv, pubkey: hex(b32(pub.x)) };
}

const makeEvent = (kind, dTag, content, extraTags = []) => signEvent(kind, [['d', dTag], ...extraTags], content);

// ────────────────────────────── חיבור לממסרים ──────────────────────────────

// ─────────── Θ: מד-בריאות ממסרים (EWMA + Phi-Accrual) ───────────
// לכל ממסר נמדד זמן-תגובה (ממוצע נע מעריכי) ורצף הצלחות/כשלים. גלאי
// הכשל φ (phi-accrual, כמו ב-Cassandra/Akka) מחשב "רמת חשד" רציפה לפי
// התפלגות מרווחי-ההגעה — לא בוליאני "חי/מת" אלא הסתברות מתמטית. המנוע
// בוחר אוטומטית לשגר קודם לממסרים הבריאים ביותר ומדלג על חשודים.
class RelayHealth {
  constructor() { this.ewma = 400; this.ok = 1; this.fail = 0; this.last = Date.now(); this.iv = [700]; }
  observe() { // הגעת הודעה כלשהי — מזין את גלאי ה-φ
    const t = Date.now(), gap = t - this.last; this.last = t;
    if (gap > 0 && gap < 120_000) { this.iv.push(gap); if (this.iv.length > 40) this.iv.shift(); }
  }
  latency(ms) { this.ewma = 0.7 * this.ewma + 0.3 * ms; this.ok++; }
  miss() { this.fail++; }
  phi() { // רמת חשד: ככל שעבר זמן רב יותר מהצפוי, φ גדל
    const mean = this.iv.reduce((s, x) => s + x, 0) / this.iv.length || 700;
    const t = Date.now() - this.last;
    return (t / mean) / Math.LN10; // התפלגות מעריכית: φ = -log10(e^{-t/μ})
  }
  score() { // גבוה = בריא: יחס הצלחה גבוה, זמן-תגובה נמוך, חשד נמוך
    const rate = this.ok / (this.ok + this.fail);
    return (rate * 1000) / (1 + this.ewma / 100) / (1 + Math.max(0, this.phi() - 1));
  }
}

class RelayPool {
  constructor(urls, fallback = []) {
    this.urls = urls;
    this.fallback = fallback;      // נתיבים חלופיים ל-hopping (Auto-Proxy)
    this.active = new Set(urls);   // מסלולים פעילים כרגע
    this.sockets = new Map();      // url -> WebSocket
    this.pendingOk = new Map();    // eventId -> {resolve, oks:Set}
    this.subs = new Map();         // subId -> {onEvent, eose:Set, onEose}
    this.health = new Map();       // url -> RelayHealth (Θ)
    this.hops = 0;                 // כמה פעמים הוחלף מסלול
    this.closed = false;
  }

  _h(url) { let h = this.health.get(url); if (!h) { h = new RelayHealth(); this.health.set(url, h); } return h; }

  /** החלטת החלפת-מסלול: פונקציה טהורה, ניתנת לבדיקה */
  _hopDecision(report, connected) {
    if (connected < 3) return true;                       // מעט מדי חיבורים
    const suspect = report.filter((r) => r.phi > 6).length;
    return report.length > 0 && suspect / report.length >= 0.5; // רוב הנתיב מתדרדר
  }

  /** מנתח מסלולים בזמן אמת ומחליף לנתיבים חלופיים בעת חסימה/הידרדרות */
  autoHop() {
    if (this.closed) return;
    const report = this.healthReport();
    if (!this._hopDecision(report, this.sockets.size)) return;
    const spare = this.fallback.filter((u) => !this.active.has(u));
    if (!spare.length) return;
    for (const url of shuffle(spare).slice(0, 4)) { this.active.add(url); this._connect(url); }
    this.hops++;
    log(`Θ: החלפת-מסלול דינמית (#${this.hops}) — נוספו נתיבים חלופיים בעקבות הידרדרות/חסימה.`);
  }

  /** סדר הממסרים לפי בריאות (הבריא ביותר ראשון) — לשיגור מועדף */
  healthyUrls() {
    return [...this.sockets.keys()].sort((a, b) => this._h(b).score() - this._h(a).score());
  }

  /** תמונת בריאות הרשת — לניטור עצמי */
  healthReport() {
    return [...this.sockets.keys()].map((u) => {
      const h = this._h(u);
      return { relay: u, latency: Math.round(h.ewma), phi: +h.phi().toFixed(1), score: Math.round(h.score()) };
    }).sort((a, b) => b.score - a.score);
  }

  connectAll() {
    // ערבוב סדר החיבור + ריצוד תזמון — טביעת-רגל מטמורפית בכל הרצה
    shuffle(this.urls).forEach((url, i) => setTimeout(() => this._connect(url), metaJitter(i * 15, 120)));
  }

  _connect(url, attempt = 0) {
    if (this.closed) return;
    let ws;
    try { ws = new WebSocket(url); } catch { return; }
    ws.onopen = () => {
      this.sockets.set(url, ws);
      // רישום מנויים קיימים גם על חיבור חדש
      for (const [subId, sub] of this.subs) {
        try { ws.send(JSON.stringify(['REQ', subId, ...sub.filters])); } catch {}
      }
    };
    ws.onmessage = (m) => {
      this._h(url).observe(); // Θ: הזנת גלאי ה-φ בכל הודעה נכנסת
      let msg;
      try { msg = JSON.parse(m.data); } catch { return; }
      if (msg[0] === 'OK') {
        const p = this.pendingOk.get(msg[1]);
        if (p && msg[2] === true) {
          if (!p.oks.has(url)) this._h(url).latency(Date.now() - (p.sentAt ?? Date.now())); // Θ: זמן-תגובה
          p.oks.add(url);
          if (p.oks.size >= p.target) p.resolve(p.oks.size);
        }
      } else if (msg[0] === 'EVENT') {
        // Σ3: כל אירוע מאומת מתמטית (hash + Schnorr) לפני שנכנס למערכת
        const sub = this.subs.get(msg[1]);
        if (sub && msg[2]) {
          if (!verifyEvent(msg[2])) {
            if (++rejectedEvents % 10 === 1) log(`Σ3: נדחה אירוע עם חתימה/זהות לא תקינה מ-${url}`);
            return;
          }
          sub.onEvent(msg[2], url);
        }
      } else if (msg[0] === 'EOSE') {
        const sub = this.subs.get(msg[1]);
        if (sub) { sub.eose.add(url); sub.onEose?.(sub.eose.size); }
      }
    };
    ws.onclose = () => {
      this.sockets.delete(url);
      if (!this.closed) setTimeout(() => this._connect(url, attempt + 1), Math.min(30_000, 1000 * 2 ** Math.min(attempt, 5)));
    };
    ws.onerror = () => { try { ws.close(); } catch {} };
  }

  async waitForConnections(min = 3, timeoutMs = 10_000) {
    const t0 = Date.now();
    while (this.sockets.size < min && Date.now() - t0 < timeoutMs) await sleep(150);
    return this.sockets.size;
  }

  /** שיגור אירוע לכל הממסרים; מסתיים כש-minOks ממסרים אישרו קליטה */
  publish(ev, timeoutMs = 12_000, minOks = 1) {
    return new Promise((resolve, reject) => {
      const target = Math.min(minOks, Math.max(1, this.sockets.size));
      const entry = {
        oks: new Set(),
        sentAt: Date.now(),
        resolve: (count) => { clearTimeout(timer); this.pendingOk.delete(ev.id); resolve(count); },
        target,
      };
      const timer = setTimeout(() => {
        this.pendingOk.delete(ev.id);
        // Θ: ממסרים שלא אישרו נספרים ככשל — מוריד את ציון הבריאות שלהם
        for (const u of this.sockets.keys()) if (!entry.oks.has(u)) this._h(u).miss();
        entry.oks.size > 0 ? resolve(entry.oks.size) : reject(new Error('no relay accepted event'));
      }, timeoutMs);
      this.pendingOk.set(ev.id, entry);
      const frame = JSON.stringify(['EVENT', ev]);
      let sent = 0;
      // Θ: משגרים קודם לבריאים ביותר (אך לכולם — לחוסן)
      for (const url of this.healthyUrls()) { const ws = this.sockets.get(url); try { ws.send(frame); sent++; } catch {} }
      if (sent === 0) { clearTimeout(timer); this.pendingOk.delete(ev.id); reject(new Error('no open relay connections')); }
    });
  }

  /** משיכה חד-פעמית: מחזיר את האירועים העדכניים ביותר לכל d-tag */
  query(filters, timeoutMs = 9_000) {
    return new Promise((resolve) => {
      const subId = 'q' + hex(crypto.randomBytes(4));
      const latest = new Map(); // dTag -> event
      const finish = () => { this.subs.delete(subId); this._closeSub(subId); resolve([...latest.values()]); };
      const timer = setTimeout(finish, timeoutMs);
      let target = Math.max(1, Math.floor(this.sockets.size / 2));
      this.subs.set(subId, {
        filters,
        eose: new Set(),
        onEvent: (ev) => {
          const d = ev.tags.find((t) => t[0] === 'd')?.[1] ?? '';
          const cur = latest.get(d);
          if (!cur || ev.created_at > cur.created_at) latest.set(d, ev);
        },
        onEose: (count) => { if (count >= target) { clearTimeout(timer); setTimeout(finish, 400); } },
      });
      const frame = JSON.stringify(['REQ', subId, ...filters]);
      for (const ws of this.sockets.values()) { try { ws.send(frame); } catch {} }
    });
  }

  /** מנוי חי — קבלת עדכונים בזמן אמת */
  subscribe(filters, onEvent) {
    const subId = 's' + hex(crypto.randomBytes(4));
    const seen = new Set();
    this.subs.set(subId, {
      filters,
      eose: new Set(),
      onEvent: (ev) => { if (!seen.has(ev.id)) { seen.add(ev.id); onEvent(ev); } },
    });
    const frame = JSON.stringify(['REQ', subId, ...filters]);
    for (const ws of this.sockets.values()) { try { ws.send(frame); } catch {} }
    return subId;
  }

  _closeSub(subId) {
    const frame = JSON.stringify(['CLOSE', subId]);
    for (const ws of this.sockets.values()) { try { ws.send(frame); } catch {} }
  }

  close() {
    this.closed = true;
    for (const ws of this.sockets.values()) { try { ws.close(); } catch {} }
    this.sockets.clear();
  }
}

// ────────────────────────────── מצב מקומי ──────────────────────────────

function loadState() {
  try { return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); } catch { return { files: {}, kv: {} }; }
}
function saveState(state) {
  try { fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2)); } catch (err) { log('אזהרה: שמירת מצב נכשלה:', err.message); }
}

async function scanLocal() {
  const files = new Map(); // relPath -> {hash, size, mtime}
  async function walk(dir) {
    let entries;
    try { entries = await fsp.readdir(dir, { withFileTypes: true }); } catch { return; }
    for (const e of entries) {
      if (IGNORE.includes(e.name) || e.name.startsWith('.cloud-')) continue;
      const full = path.join(dir, e.name);
      const rel = path.relative(ROOT, full).replace(/\\/g, '/');
      if (e.isDirectory()) await walk(full);
      else if (e.isFile()) {
        try {
          const st = await fsp.stat(full);
          const data = await fsp.readFile(full);
          files.set(rel, { hash: hex(sha256(data)), size: st.size, mtime: Math.floor(st.mtimeMs) });
        } catch {}
      }
    }
  }
  await walk(ROOT);
  return files;
}

// ────────────────────────────── מנוע הסינכרון ──────────────────────────────

const npub = () => bech32Encode('npub', to5bit(unhex(keys.pubkey)));

/** הכתובת הגלובלית הקבועה של הפרויקט — עובדת גם כשכל המכשירים כבויים */
function globalAddress() {
  if (CUSTOM_DOMAIN) return `https://${CUSTOM_DOMAIN}`;
  if (PUBLIC_WEBSITE) return `https://${npub()}.${NSITE_GATEWAYS[0]}`;
  const naddr = makeNaddr(dSite(), keys.pubkey, KIND_SITE, 'wss://relay.damus.io');
  return 'https://yakihonne.com/article/' + naddr;
}

/** כתובות מראה — אותו אתר מוגש מכמה שערים בלתי-תלויים */
function mirrorAddresses() {
  return NSITE_GATEWAYS.map((g) => `https://${npub()}.${g}`);
}

/** הוראות DNS מדויקות לחיבור דומיין אמיתי משלך (למשל www.myproject.io) */
function printDomainSetup() {
  const host = CUSTOM_DOMAIN.replace(/^https?:\/\//, '');
  const label = host.split('.')[0];
  // היעד חייב להיות שער שתומך בדומיין מותאם + תעודת TLS אוטומטית.
  // nsite.lol (מימוש הייחוס של nsite-gateway) מתעד תמיכה ב-CNAME + on-demand TLS.
  const target = `${npub()}.nsite.lol`;
  log('─────────────────────────────────────────────');
  log(`חיבור הדומיין ${host} לאתר המבוזר — הגדרה חד-פעמית ברשם הדומיינים:`);
  log(`  סוג רשומה: CNAME`);
  log(`  שם/Host:   ${label}`);
  log(`  יעד/Value: ${target}`);
  log('אחרי שהרשומה מתעדכנת (עד כמה שעות), האתר יוגש מ:');
  log(`  https://${host}  — עם HTTPS אמיתי, שם קצר, נראה כאתר רגיל.`);
  log('הערה: השער צריך לתמוך בדומיינים מותאמים + TLS אוטומטי (כמו nsite.lol).');
  log('─────────────────────────────────────────────');
}

/**
 * מנוע SkyLattice לדפדפן — מתפרסם אוטומטית כ-/cloud-web.js בכל אתר.
 * הופך את הכתובת הגלובלית למערכת שלמה בלי שום שרת:
 *
 *   נתונים חיים:   SkyCloud.save / load / watch / onChange
 *   משתמשים:       SkyCloud.register / login / logout / user  (כמו Firebase Auth,
 *                  אבל הזהות נגזרת מתמטית — PBKDF2 310K איטרציות -> Schnorr+AES,
 *                  אין מסד משתמשים בשום מקום, אי אפשר לגנוב מה שלא מאוחסן)
 *   אחסון פרטי:    SkyCloud.userSet / userGet  (מוצפן AES-256-GCM לכל משתמש)
 *   קבצים:         SkyCloud.upload(file)  — העלאה מהדפדפן לאחסון המבוזר, URL קבוע
 *   חוות שרתים:    SkyCloud.serve({task: fn}) / SkyCloud.run(task, payload)
 *                  כל טאב פתוח וכל מכשיר Node הם עובדי-חישוב; בחירת המבצע
 *                  דטרמיניסטית (Rendezvous Hashing) — בלי מתאם מרכזי.
 */
function buildWebSdk() {
  return `/* SkyCloud / SkyLattice Web SDK (auto-generated) — decentralized data, users, files, compute. No server. */
(() => {
'use strict';
if (window.SkyCloud) return;
const PROJECT = '${keys.pubkey}';
const RELAYS = ${JSON.stringify(RELAYS.slice(0, 8))};
const KIND = ${KIND_DATA};
const BLOSSOM = ${JSON.stringify(BLOSSOM_SERVERS)};
const API_PREFIX = ${JSON.stringify(API_PREFIX)};
const P16 = PROJECT.slice(0, 16);
const PRE = { kv: 'scweb:' + P16 + ':', usr: 'scusr:' + P16 + ':', ud: 'scud:' + P16 + ':', job: 'scjob:' + P16 + ':', res: 'scres:' + P16 + ':', dm: 'scdm:' + P16 + ':', api: 'scapi:' + P16 + ':', apx: 'scapx:' + P16 + ':', snp: 'scsnp:' + P16 + ':', pub: 'scpub:' + P16 + ':' };
const DPREFIX = PRE.kv;

/* secp256k1 + Schnorr (BIP-340) in pure JS */
const Pf = 2n ** 256n - 2n ** 32n - 977n;
const Nn = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141');
const G = {
  x: BigInt('0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'),
  y: BigInt('0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8'),
};
const mod = (a, m) => ((a % m) + m) % m;
const powmod = (b, e, m) => { let r = 1n; b = mod(b, m); while (e > 0n) { if (e & 1n) r = (r * b) % m; b = (b * b) % m; e >>= 1n; } return r; };
const inv = (a) => powmod(mod(a, Pf), Pf - 2n, Pf);
const ptDouble = (a) => { if (!a) return null; const l = mod(3n * a.x * a.x * inv(2n * a.y), Pf); const x = mod(l * l - 2n * a.x, Pf); return { x, y: mod(l * (a.x - x) - a.y, Pf) }; };
const ptAdd = (a, b) => { if (!a) return b; if (!b) return a; if (a.x === b.x) return a.y === b.y ? ptDouble(a) : null; const l = mod((b.y - a.y) * inv(mod(b.x - a.x, Pf)), Pf); const x = mod(l * l - a.x - b.x, Pf); return { x, y: mod(l * (a.x - x) - a.y, Pf) }; };
const ptMul = (pt, k) => { let r = null, a = pt; while (k > 0n) { if (k & 1n) r = ptAdd(r, a); a = ptDouble(a); k >>= 1n; } return r; };

const enc = new TextEncoder();
const dec = new TextDecoder();
const concatB = (arrs) => { const t = arrs.reduce((s, a) => s + a.length, 0); const out = new Uint8Array(t); let o = 0; for (const a of arrs) { out.set(a, o); o += a.length; } return out; };
const sha256 = async (...chunks) => new Uint8Array(await crypto.subtle.digest('SHA-256', concatB(chunks)));
const hex = (u8) => Array.from(u8, (b) => b.toString(16).padStart(2, '0')).join('');
const unhex = (s) => new Uint8Array(s.match(/../g).map((h) => parseInt(h, 16)));
const big = (u8) => BigInt('0x' + hex(u8));
const b32 = (n) => unhex(n.toString(16).padStart(64, '0'));
const taggedHash = async (tag, ...data) => { const th = await sha256(enc.encode(tag)); return sha256(th, th, ...data); };
const xorB = (a, b) => a.map((v, i) => v ^ b[i]);
const b64 = (u8) => { let s = ''; for (let i = 0; i < u8.length; i += 8192) s += String.fromCharCode.apply(null, u8.subarray(i, i + 8192)); return btoa(s); };
const unb64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));

async function schnorrSign(msg32, dIn) {
  let d = dIn;
  const Pub = ptMul(G, d);
  if (Pub.y % 2n !== 0n) d = Nn - d;
  const px = b32(Pub.x);
  const aux = crypto.getRandomValues(new Uint8Array(32));
  const t = xorB(b32(d), await taggedHash('BIP0340/aux', aux));
  const k0 = mod(big(await taggedHash('BIP0340/nonce', t, px, msg32)), Nn);
  const R = ptMul(G, k0);
  const k = R.y % 2n === 0n ? k0 : Nn - k0;
  const rx = b32(R.x);
  const e = mod(big(await taggedHash('BIP0340/challenge', rx, px, msg32)), Nn);
  return concatB([rx, b32(mod(k + e * d, Nn))]);
}

/* Σ3: אימות Schnorr מלא של כל אירוע נכנס — אפס אמון בממסרים */
function liftX(x) {
  if (x >= Pf) return null;
  const c = mod(x * x * x + 7n, Pf);
  const y = powmod(c, (Pf + 1n) / 4n, Pf);
  if ((y * y) % Pf !== c) return null;
  return { x, y: y % 2n === 0n ? y : Pf - y };
}
const verified = new Set();
async function verifyEvent(ev) {
  try {
    if (!ev || typeof ev.id !== 'string' || typeof ev.sig !== 'string' || typeof ev.pubkey !== 'string') return false;
    /* בדיקת ה-hash רצה תמיד — מונעת replay של תוכן זר תחת id מאומת */
    const calc = hex(await sha256(enc.encode(JSON.stringify([0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content]))));
    if (calc !== ev.id) return false;
    if (verified.has(ev.id)) return true;
    const sig = unhex(ev.sig);
    if (sig.length !== 64) return false;
    const r = big(sig.slice(0, 32));
    const s = big(sig.slice(32));
    if (r >= Pf || s >= Nn) return false;
    const Ppt = liftX(big(unhex(ev.pubkey)));
    if (!Ppt) return false;
    const e = mod(big(await taggedHash('BIP0340/challenge', sig.slice(0, 32), unhex(ev.pubkey), unhex(ev.id))), Nn);
    const R = ptAdd(ptMul(G, s), ptMul(Ppt, Nn - e));
    if (!R || R.y % 2n !== 0n || R.x !== r) return false;
    if (verified.size > 4000) verified.clear();
    verified.add(ev.id);
    return true;
  } catch { return false; }
}

/* ─── מאגרי נתונים חיים ─── */
const sockets = new Set();
const kv = {};        /* נתונים משותפים (ציבורי) */
const usersReg = {};  /* name -> {pub, ts} — הרשמה מוקדמת מנצחת */
const userData = {};  /* pub16:key -> {iv, ct, ts} — מוצפן פר-משתמש */
const results = {};   /* jobId -> תוצאת חישוב */
const resWaiters = {};
const apiDone = {};   /* Ω: reqId -> תגובת API */
const apiWaiters = {};
const snaps = {};     /* Ω: snapHash -> תמונת-API סטטית (נענית גם ללא עובד חי) */
const sitePolicies = {}; /* ∇: slug -> מדיניות-DAG ציבורית (הרצה אצל הלקוח) */
const apiSeen = new Set();
let apiRoutes = null; /* Ω: נתיבי-שרת שהטאב הזה מגיש (אם הופעל serveApi) */
let handlers = null;
const dms = [];       /* הודעות מוצפנות מקצה-לקצה (Ψ) שפוענחו */
const rawDms = [];    /* מעטפות גולמיות — נסרקות שוב אחרי login */
const dmSeen = new Set();
const dmWatchers = [];
const globalWatchers = [];
const keyWatchers = {};
let readyResolve;
const ready = new Promise((r) => { readyResolve = r; });

/* ─── זהות: מבקר אנונימי כברירת מחדל; משתמש רשום אחרי login ─── */
const storeKey = 'skycloud_visitor_' + PROJECT.slice(0, 8);
const sessKey = 'skycloud_user_' + PROJECT.slice(0, 8);
let seedHex = null;
try { seedHex = localStorage.getItem(storeKey); } catch {}
if (!seedHex || seedHex.length !== 64) {
  seedHex = hex(crypto.getRandomValues(new Uint8Array(32)));
  try { localStorage.setItem(storeKey, seedHex); } catch {}
}
const visitorPriv = mod(big(unhex(seedHex)), Nn - 1n) + 1n;
const visitorPub = hex(b32(ptMul(G, visitorPriv).x));
let cur = { priv: visitorPriv, pub: visitorPub, aes: null, name: null };

async function makeEvent(tags, content, kind) {
  const ev = { pubkey: cur.pub, created_at: Math.floor(Date.now() / 1000), kind: kind || KIND, tags, content };
  const idBytes = await sha256(enc.encode(JSON.stringify([0, ev.pubkey, ev.created_at, ev.kind, ev.tags, ev.content])));
  ev.id = hex(idBytes);
  ev.sig = hex(await schnorrSign(idBytes, cur.priv));
  return ev;
}
function publish(ev) {
  const frame = JSON.stringify(['EVENT', ev]);
  let sent = 0;
  for (const ws of sockets) { try { ws.send(frame); sent++; } catch {} }
  if (!sent) throw new Error('SkyCloud: no relay connection');
  return ev.id;
}

/* ─── מיזוג אירועים מהרשת: נתונים / משתמשים / אחסון פרטי / חישוב ─── */
function merge(ev, notify) {
  const d = (ev.tags.find((t) => t[0] === 'd') || [])[1] || '';
  let entry;
  try { entry = JSON.parse(ev.content); } catch { return; }
  if (!entry || typeof entry.ts !== 'number') return;
  if (d.indexOf(PRE.kv) === 0) {
    const key = d.slice(PRE.kv.length);
    const c = kv[key];
    if (c && c.ts >= entry.ts) return;
    kv[key] = entry;
    if (!notify) return;
    for (const fn of globalWatchers) { try { fn(key, entry.v, ev.pubkey); } catch {} }
    for (const fn of keyWatchers[key] || []) { try { fn(entry.v, ev.pubkey); } catch {} }
  } else if (d.indexOf(PRE.usr) === 0) {
    const nm = String(entry.name || '').toLowerCase();
    const c = usersReg[nm];
    /* ההרשמה המוקדמת ביותר מנצחת — אי אפשר להשתלט על שם תפוס */
    if (!c || entry.ts < c.ts) usersReg[nm] = { pub: ev.pubkey, ts: entry.ts };
  } else if (d.indexOf(PRE.ud) === 0) {
    const k = d.slice(PRE.ud.length);
    const c = userData[k];
    if (!c || entry.ts > c.ts) userData[k] = entry;
  } else if (d.indexOf(PRE.res) === 0) {
    const id = d.slice(PRE.res.length);
    if (!results[id] || entry.ts > results[id].ts) results[id] = entry;
    const w = resWaiters[id];
    if (w) { delete resWaiters[id]; w(results[id]); }
  } else if (d.indexOf(PRE.job) === 0) {
    const id = d.slice(PRE.job.length);
    if (notify && handlers && !results[id] && Date.now() - entry.ts < 60000) maybeRun(id, entry);
  } else if (d.indexOf(PRE.apx) === 0) {
    /* Ω: תגובת API חזרה מעובד-שרת מבוזר */
    const id = d.slice(PRE.apx.length);
    if (!apiDone[id]) { apiDone[id] = entry; const w = apiWaiters[id]; if (w) { delete apiWaiters[id]; w(entry); } }
  } else if (d.indexOf(PRE.snp) === 0) {
    /* Ω: תמונת-API סטטית — נשמרת לשירות ללא-מפעיל (LWW לפי ts) */
    const h = d.slice(PRE.snp.length);
    if (!snaps[h] || entry.ts > snaps[h].ts) snaps[h] = entry;
  } else if (d.indexOf(PRE.pub) === 0) {
    /* ∇: מדיניות אתר-חי ציבורית — הלקוח מריץ כל סט-קלטים אצל עצמו */
    const slug = entry.name || d.slice(PRE.pub.length);
    if (!sitePolicies[slug] || entry.ts >= (sitePolicies[slug].ts || 0)) sitePolicies[slug] = entry;
  } else if (d.indexOf(PRE.api) === 0) {
    /* Ω: בקשת API — אם הטאב הזה מגיש נתיבים, הוא עובד-שרת בעצמו */
    if (notify && apiRoutes && entry.from !== cur.pub && Date.now() - entry.ts < 30000) serveApiReq(d.slice(PRE.api.length), entry);
  } else if (d.indexOf(PRE.dm) === 0) {
    rawDms.push({ ev, d, env: entry });
    handleDm(ev, d, entry, notify);
  }
}

/* ─── Ψ: "ערבוב הצבעים" — סוד משותף ECDH: (dA·PB).x == (dB·PA).x ─── */
async function ecdhKey(pubHex) {
  const Ppt = liftX(big(unhex(pubHex)));
  if (!Ppt) throw new Error('SkyCloud: bad recipient id');
  const S = ptMul(Ppt, cur.priv);
  const raw = await sha256(enc.encode('skylattice-psi:v1:'), b32(S.x));
  return crypto.subtle.importKey('raw', raw, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
}
async function handleDm(ev, d, env, notify) {
  try {
    if (typeof env.ct !== 'string' || typeof env.to !== 'string') return;
    const mine = env.to === cur.pub;
    const fromMe = ev.pubkey === cur.pub;
    if (!mine && !fromMe) return; /* לא מיועד לזהות הנוכחית */
    const seenKey = d + ':' + cur.pub;
    if (dmSeen.has(seenKey)) return;
    const key = await ecdhKey(mine ? ev.pubkey : env.to);
    const raw = unb64(env.ct);
    const pt = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: raw.slice(0, 12), additionalData: enc.encode('sc-psi:' + d) },
      key, raw.slice(12));
    const payload = JSON.parse(dec.decode(new Uint8Array(pt)));
    dmSeen.add(seenKey);
    const msg = { from: ev.pubkey, to: env.to, value: payload.v, ts: payload.ts };
    dms.push(msg);
    dms.sort((a, b) => a.ts - b.ts);
    if (notify) for (const fn of dmWatchers) { try { fn(msg); } catch {} }
  } catch {} /* פענוח נכשל = לא שלנו / חבלה */
}

/* בחירת מבצע דטרמיניסטית (Rendezvous Hashing): לכל עובד ציון sha256(worker:job);
   ציון נמוך רץ מוקדם יותר, והראשון שמפרסם תוצאה עוצר את כל השאר — בלי מתאם מרכזי */
async function maybeRun(id, job) {
  const fn = handlers[job.task];
  if (!fn) return;
  const rank = Number(big(await sha256(enc.encode(cur.pub + ':' + id))) % 1500n);
  await new Promise((r) => setTimeout(r, 450 + rank));
  if (results[id]) return; /* עובד אחר (מכשיר Node או טאב) כבר ענה */
  let res;
  try { res = { ok: true, value: await fn(job.payload, job) }; }
  catch (e) { res = { ok: false, error: String((e && e.message) || e) }; }
  res.ts = Date.now();
  res.worker = cur.pub;
  results[id] = res;
  try { publish(await makeEvent([['d', PRE.res + id], ['p', PROJECT]], JSON.stringify(res))); } catch {}
}

/* Ω: hash-נתיב לתמונה סטטית — זהה לצד ה-Node (sha256('snap:'+path), 24 hex) */
async function snapHash(path) { return hex(await sha256(enc.encode('snap:' + path))).slice(0, 24); }

/* Ω: התאמת נתיב מבין הנתיבים שהטאב הזה מגיש (serveApi) */
function matchApiRoute(method, pathname) {
  for (const r of apiRoutes) {
    if (r.method !== method && r.method !== 'ALL') continue;
    if (r.path instanceof RegExp) { const m = pathname.match(r.path); if (m) return { r, params: m.groups || {} }; }
    else if (r.path === pathname) return { r, params: {} };
  }
  return null;
}

/* Ω: הטאב הזה עונה כעובד-שרת — בחירת מבצע דטרמיניסטית (Rendezvous) */
async function serveApiReq(id, req) {
  if (apiSeen.has(id)) return;
  const rank = Number(big(await sha256(enc.encode(cur.pub + ':' + id))) % 700n);
  await new Promise((r) => setTimeout(r, 150 + rank));
  if (apiSeen.has(id)) return;
  apiSeen.add(id); if (apiSeen.size > 3000) apiSeen.clear();
  const pathname = String(req.path || '').split('?')[0];
  const hit = matchApiRoute(req.method, pathname);
  let res;
  if (!hit) res = { status: 404, headers: { 'content-type': 'application/json' }, body: b64(enc.encode(JSON.stringify({ error: 'no route', path: req.path }))) };
  else {
    try {
      const bodyBytes = req.body ? unb64(req.body) : new Uint8Array(0);
      const out = await hit.r.fn({ method: req.method, path: req.path, headers: req.headers || {}, params: hit.params, body: bodyBytes, json: () => JSON.parse(dec.decode(bodyBytes) || 'null'), text: () => dec.decode(bodyBytes) });
      let status = 200, headers = { 'content-type': 'application/json' }, payload = out;
      if (out && typeof out === 'object' && ('status' in out || 'body' in out || 'headers' in out)) { status = out.status || 200; headers = out.headers || headers; payload = out.body; }
      let bytes;
      if (payload instanceof Uint8Array) bytes = payload;
      else if (typeof payload === 'string') bytes = enc.encode(payload);
      else { bytes = enc.encode(JSON.stringify(payload == null ? null : payload)); headers['content-type'] = headers['content-type'] || 'application/json'; }
      res = { status, headers, body: b64(bytes) };
    } catch (e) { res = { status: 500, headers: { 'content-type': 'application/json' }, body: b64(enc.encode(JSON.stringify({ error: String((e && e.message) || e) }))) }; }
  }
  res.ts = Date.now(); res.worker = cur.pub;
  try { publish(await makeEvent([['d', PRE.apx + id], ['p', PROJECT]], JSON.stringify(res))); } catch {}
}

/* ─── משתמשים: הזהות נגזרת מתמטית משם+סיסמה, לא מאוחסנת בשום מקום ─── */
async function deriveUser(name, pass) {
  const nm = String(name || '').trim().toLowerCase();
  if (!nm || !pass) throw new Error('SkyCloud: username and password required');
  const km = await crypto.subtle.importKey('raw', enc.encode(String(pass)), 'PBKDF2', false, ['deriveBits']);
  const bits = new Uint8Array(await crypto.subtle.deriveBits(
    { name: 'PBKDF2', hash: 'SHA-256', salt: enc.encode('skylattice:u1:' + PROJECT + ':' + nm), iterations: 310000 },
    km, 512));
  const priv = mod(big(bits.slice(0, 32)), Nn - 1n) + 1n;
  const pub = hex(b32(ptMul(G, priv).x));
  return { nm, bits, pub };
}
async function activate(nm, bits) {
  const priv = mod(big(bits.slice(0, 32)), Nn - 1n) + 1n;
  const pub = hex(b32(ptMul(G, priv).x));
  const aes = await crypto.subtle.importKey('raw', bits.slice(32), { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
  cur = { priv, pub, aes, name: nm };
  try { localStorage.setItem(sessKey, JSON.stringify({ n: nm, b: hex(bits) })); } catch {}
  /* Ψ: סריקה מחדש של מעטפות מוצפנות — הודעות שממתינות לזהות הזאת */
  for (const r of rawDms) handleDm(r.ev, r.d, r.env, true);
  return { name: nm, id: pub };
}
function toVisitor() {
  cur = { priv: visitorPriv, pub: visitorPub, aes: null, name: null };
  try { localStorage.removeItem(sessKey); } catch {}
}
/* שחזור התחברות מביקור קודם */
try {
  const s = JSON.parse(localStorage.getItem(sessKey) || 'null');
  if (s && s.n && s.b && s.b.length === 128) activate(s.n, unhex(s.b));
} catch {}

/* ─── העלאת קבצים מהדפדפן לאחסון המבוזר (BUD-02, חתום) ─── */
async function upload(data, type) {
  let bytes;
  if (typeof Blob !== 'undefined' && data instanceof Blob) { type = type || data.type; bytes = new Uint8Array(await data.arrayBuffer()); }
  else if (typeof data === 'string') { bytes = enc.encode(data); type = type || 'text/plain'; }
  else if (data instanceof Uint8Array) bytes = data;
  else bytes = new Uint8Array(data);
  type = type || 'application/octet-stream';
  const hash = hex(await sha256(bytes));
  const auth = await makeEvent([['t', 'upload'], ['x', hash], ['expiration', String(Math.floor(Date.now() / 1000) + 600)]], 'upload blob', 24242);
  const hdr = 'Nostr ' + btoa(JSON.stringify(auth));
  const urls = [];
  await Promise.allSettled(BLOSSOM.map(async (s) => {
    try {
      const head = await fetch(s + '/' + hash, { method: 'HEAD' });
      if (!head.ok) {
        const r = await fetch(s + '/upload', { method: 'PUT', headers: { Authorization: hdr, 'Content-Type': type }, body: bytes });
        if (!r.ok) return;
      }
      urls.push(s + '/' + hash);
    } catch {}
  }));
  if (!urls.length) throw new Error('SkyCloud: upload failed on all storage servers');
  return { hash, url: urls[0], urls, size: bytes.length, type };
}

/* ─── חיבור לממסרי הרשת ─── */
function connect(url) {
  let ws;
  try { ws = new WebSocket(url); } catch { return; }
  ws.onopen = () => {
    sockets.add(ws);
    ws.send(JSON.stringify(['REQ', 'scw', { kinds: [KIND], '#p': [PROJECT] }]));
  };
  ws.onmessage = async (m) => {
    let msg;
    try { msg = JSON.parse(m.data); } catch { return; }
    if (msg[0] === 'EVENT' && msg[2]) {
      if (await verifyEvent(msg[2])) merge(msg[2], true); /* Σ3: זיוף נדחה מתמטית */
    } else if (msg[0] === 'EOSE') setTimeout(readyResolve, 300); /* מרווח לאימותים אסינכרוניים שבדרך */
  };
  ws.onclose = () => { sockets.delete(ws); setTimeout(() => connect(url), 3000 + Math.random() * 4000); };
  ws.onerror = () => { try { ws.close(); } catch {} };
}
RELAYS.forEach(connect);
setTimeout(() => readyResolve(), 8000); /* גם בלי EOSE — לא נתקעים */

/* ─── Ω: שרת אפליקציה חי — כל fetch('/api/…') מנותב לרשת המבוזרת ─── */
async function apiCall(path, init) {
  await ready;
  init = init || {};
  const method = (init.method || 'GET').toUpperCase();
  let bodyB64 = '';
  if (init.body != null) {
    const b = typeof init.body === 'string' ? enc.encode(init.body)
      : (init.body instanceof Uint8Array ? init.body : enc.encode(JSON.stringify(init.body)));
    bodyB64 = b64(b);
  }
  const id = hex(crypto.getRandomValues(new Uint8Array(8)));
  const sh = await snapHash(path);
  const req = { method, path, headers: init.headers || {}, body: bodyB64, ts: Date.now(), from: cur.pub };
  publish(await makeEvent([['d', PRE.api + id], ['p', PROJECT]], JSON.stringify(req)));
  const res = await new Promise((resolve, reject) => {
    let done = false;
    const finish = (r) => { if (!done) { done = true; clearTimeout(hard); clearTimeout(fb); delete apiWaiters[id]; resolve(r); } };
    /* מנצח: עובד-שרת חי אם ענה מהר; אחרת נופלים לתמונה הסטטית מהרשת (ללא-מפעיל) */
    const hard = setTimeout(() => { if (done) return; done = true; delete apiWaiters[id]; if (snaps[sh]) resolve(snaps[sh]); else reject(new Error('SkyCloud: no server peer / snapshot for ' + path)); }, init.timeoutMs || 25000);
    const fb = setTimeout(() => { if (!done && (method === 'GET' || method === 'HEAD') && snaps[sh]) finish(snaps[sh]); }, init.staticFallbackMs || 2200);
    if (apiDone[id]) return finish(apiDone[id]);
    apiWaiters[id] = finish;
  });
  let bodyBytes;
  if (res.blob) bodyBytes = new Uint8Array(await (await fetch(res.blob)).arrayBuffer());
  else bodyBytes = res.body ? unb64(res.body) : new Uint8Array(0);
  return new Response(bodyBytes, { status: res.status || 200, headers: res.headers || {} });
}
/* מחליף את fetch לנתיבי API באותו origin — מערכות קיימות "פשוט עובדות" */
if (typeof window !== 'undefined' && window.fetch && !window.__skyFetch) {
  window.__skyFetch = window.fetch.bind(window);
  window.fetch = (input, init) => {
    try {
      const url = typeof input === 'string' ? input : (input && input.url) || '';
      const u = new URL(url, location.href);
      if (u.origin === location.origin && u.pathname.indexOf(API_PREFIX) === 0) {
        return apiCall(u.pathname + u.search, Object.assign({ method: (input && input.method) || 'GET' }, init));
      }
    } catch {}
    return window.__skyFetch(input, init);
  };
}

/* ─── ∇: מנוע-מפרט אצל הלקוח — FormulaVM מצומצם + הרצת-DAG טהורה ─── */
const nablaSigmoid = (z) => 1 / (1 + Math.exp(-Math.max(-60, Math.min(60, z))));
class NablaVM {
  constructor() {
    this.fns = { abs: Math.abs, sqrt: Math.sqrt, min: Math.min, max: Math.max, pow: Math.pow, floor: Math.floor, ceil: Math.ceil, round: Math.round, sigmoid: nablaSigmoid, clamp: (x, lo, hi) => Math.min(hi, Math.max(lo, x)), if: (c, a, b) => (c ? a : b) };
    this.arity = { abs: 1, sqrt: 1, floor: 1, ceil: 1, round: 1, sigmoid: 1, min: 2, max: 2, pow: 2, clamp: 3, if: 3 };
    this.prec = { 'u-': 5, '^': 4, '*': 3, '/': 3, '%': 3, '+': 2, '-': 2, '<': 1, '>': 1, '<=': 1, '>=': 1, '==': 1, '!=': 1, '&&': 0, '||': 0 };
    this.right = { '^': true, 'u-': true };
  }
  eval(formula, vars) {
    const s = String(formula); const t = []; let i = 0; const ops2 = ['<=', '>=', '==', '!=', '&&', '||'];
    while (i < s.length) {
      const c = s[i]; if (c === ' ' || c === '\\t') { i++; continue; }
      if (/[0-9.]/.test(c)) { let j = i; while (j < s.length && /[0-9.]/.test(s[j])) j++; t.push({ t: 'num', v: parseFloat(s.slice(i, j)) }); i = j; continue; }
      if (/[a-zA-Z_]/.test(c)) { let j = i; while (j < s.length && /[a-zA-Z0-9_]/.test(s[j])) j++; t.push({ t: 'name', v: s.slice(i, j) }); i = j; continue; }
      const two = s.slice(i, i + 2); if (ops2.includes(two)) { t.push({ t: 'op', v: two }); i += 2; continue; }
      if ('+-*/%^()<>,'.includes(c)) { t.push({ t: c === '(' ? 'lp' : c === ')' ? 'rp' : c === ',' ? 'comma' : 'op', v: c }); i++; continue; }
      throw new Error('bad tok');
    }
    const out = [], ops = []; let prev = null;
    for (const tk of t) {
      if (tk.t === 'num') out.push(tk);
      else if (tk.t === 'name') { if (this.arity[tk.v] !== undefined) ops.push({ t: 'fn', v: tk.v }); else out.push({ t: 'var', v: tk.v }); }
      else if (tk.t === 'comma') { while (ops.length && ops[ops.length - 1].t !== 'lp') out.push(ops.pop()); }
      else if (tk.t === 'op') {
        let op = tk.v; const unary = (op === '-' || op === '+') && (prev === null || prev.t === 'op' || prev.t === 'lp' || prev.t === 'comma');
        if (unary) op = op === '-' ? 'u-' : 'u+'; if (op === 'u+') { prev = tk; continue; }
        const p = this.prec[op];
        while (ops.length) { const top = ops[ops.length - 1]; if (top.t === 'fn') { out.push(ops.pop()); continue; } if (top.t !== 'op') break; const pt = this.prec[top.v]; if (pt > p || (pt === p && !this.right[op])) out.push(ops.pop()); else break; }
        ops.push({ t: 'op', v: op });
      } else if (tk.t === 'lp') ops.push(tk);
      else if (tk.t === 'rp') { while (ops.length && ops[ops.length - 1].t !== 'lp') out.push(ops.pop()); ops.pop(); if (ops.length && ops[ops.length - 1].t === 'fn') out.push(ops.pop()); }
      prev = tk;
    }
    while (ops.length) out.push(ops.pop());
    const st = []; const bin = (f) => { const b = st.pop(), a = st.pop(); st.push(f(a, b)); };
    for (const tk of out) {
      if (tk.t === 'num') st.push(tk.v);
      else if (tk.t === 'var') st.push(Number(vars[tk.v]));
      else if (tk.t === 'fn') { const n = this.arity[tk.v], args = []; for (let k = 0; k < n; k++) args.unshift(st.pop()); st.push(this.fns[tk.v](...args)); }
      else if (tk.v === '+') bin((a, b) => a + b); else if (tk.v === '-') bin((a, b) => a - b); else if (tk.v === '*') bin((a, b) => a * b); else if (tk.v === '/') bin((a, b) => a / b); else if (tk.v === '%') bin((a, b) => a % b); else if (tk.v === '^') bin((a, b) => Math.pow(a, b)); else if (tk.v === 'u-') st.push(-st.pop());
      else if (tk.v === '<') bin((a, b) => (a < b ? 1 : 0)); else if (tk.v === '>') bin((a, b) => (a > b ? 1 : 0)); else if (tk.v === '<=') bin((a, b) => (a <= b ? 1 : 0)); else if (tk.v === '>=') bin((a, b) => (a >= b ? 1 : 0)); else if (tk.v === '==') bin((a, b) => (a === b ? 1 : 0)); else if (tk.v === '!=') bin((a, b) => (a !== b ? 1 : 0)); else if (tk.v === '&&') bin((a, b) => (a && b ? 1 : 0)); else if (tk.v === '||') bin((a, b) => (a || b ? 1 : 0));
    }
    return st[0];
  }
}
const nablaVM = new NablaVM();
const NABLA_SKIP = {};
function nablaRefs(args, ids) {
  if (!args || typeof args !== 'object') return;
  if (typeof args.$ref === 'string') { ids.add(args.$ref); return; }
  for (const k of Object.keys(args)) nablaRefs(args[k], ids);
}
function nablaPick(result, pathStr) { if (!pathStr) return result; let cur = result; for (const seg of String(pathStr).split('.')) { if (cur == null) return undefined; cur = cur[seg]; } return cur; }
function nablaSubst(args, resolved) {
  if (!args || typeof args !== 'object') return args;
  if (typeof args.$ref === 'string') return nablaPick(resolved[args.$ref], args.path);
  if (Array.isArray(args)) return args.map((v) => nablaSubst(v, resolved));
  const o = {}; for (const k of Object.keys(args)) o[k] = nablaSubst(args[k], resolved); return o;
}
function nablaHasSkip(args, resolved) {
  let bad = false;
  const walk = (v) => { if (bad || !v || typeof v !== 'object') return; if (typeof v.$ref === 'string') { if (resolved[v.$ref] === NABLA_SKIP) bad = true; return; } for (const k of Object.keys(v)) walk(v[k]); };
  walk(args); return bad;
}
function nablaOp(op, args) {
  if (op === 'formula') return nablaVM.eval(args.expr, args.vars || {});
  if (op === 'reduce') { const xs = (args.list || []).map(Number); if (args.op === 'sum') return xs.reduce((a, b) => a + b, 0); if (args.op === 'prod') return xs.reduce((a, b) => a * b, 1); if (args.op === 'min') return Math.min(...xs); if (args.op === 'max') return Math.max(...xs); if (args.op === 'mean') return xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0; }
  if (op === 'factorize') { let n = Math.floor(Number(args.n)); const f = []; for (let d = 2; d * d <= n; d++) while (n % d === 0) { f.push(d); n = n / d; } if (n > 1) f.push(n); return f; }
  if (op === 'primes') { const upTo = Math.floor(Number(args.upTo)); const sieve = new Uint8Array(upTo + 1); let count = 0, last = 0; for (let i = 2; i <= upTo; i++) if (!sieve[i]) { count++; last = i; for (let j = i * i; j <= upTo; j += i) sieve[j] = 1; } return { count, largest: last }; }
  if (op === 'matmul') { const a = args.a, b = args.b; const n = a.length, m = a[0].length, p = b[0].length; const out = Array.from({ length: n }, () => new Array(p).fill(0)); for (let i = 0; i < n; i++) for (let k = 0; k < m; k++) { const aik = a[i][k]; for (let j = 0; j < p; j++) out[i][j] += aik * b[k][j]; } return out; }
  throw new Error('nabla: unsupported op ' + op);
}
function nablaEval(dag, inputs) {
  const nodes = Object.assign({}, dag.nodes);
  if (inputs) for (const k of Object.keys(inputs)) nodes[k] = { value: inputs[k] };
  const ids = Object.keys(nodes);
  const indeg = new Map(ids.map((id) => [id, 0]));
  const edges = new Map(ids.map((id) => [id, []]));
  for (const id of ids) {
    const n = nodes[id]; const deps = new Set();
    nablaRefs({ a: n.args, w: n.when && n.when.vars, b: n.branch, c: n.coalesce, v: n.value }, deps);
    for (const dep of deps) { if (!nodes[dep]) throw new Error('nabla: missing ' + dep); edges.get(dep).push(id); indeg.set(id, indeg.get(id) + 1); }
  }
  const q = ids.filter((id) => indeg.get(id) === 0).sort(), order = [];
  while (q.length) { const id = q.shift(); order.push(id); for (const nxt of edges.get(id)) { indeg.set(nxt, indeg.get(nxt) - 1); if (indeg.get(nxt) === 0) { q.push(nxt); q.sort(); } } }
  if (order.length !== ids.length) throw new Error('nabla: cycle');
  const resolved = {}, skipped = [];
  const substVars = (vars) => { const o = {}; for (const k of Object.keys(vars || {})) { let v = nablaSubst(vars[k], resolved); if (v === NABLA_SKIP) v = 0; o[k] = v; } return o; };
  for (const id of order) {
    const n = nodes[id];
    if (n.when && !nablaVM.eval(n.when.expr, substVars(n.when.vars))) { resolved[id] = NABLA_SKIP; skipped.push(id); continue; }
    if ('value' in n) { resolved[id] = nablaSubst(n.value, resolved); continue; }
    if (n.coalesce) { let val = NABLA_SKIP; for (const ref of n.coalesce) { const v = nablaSubst(ref, resolved); if (v !== NABLA_SKIP && v !== undefined) { val = v; break; } } resolved[id] = val; if (val === NABLA_SKIP) skipped.push(id); continue; }
    if (n.branch) { const key = String(nablaVM.eval(n.branch.on, substVars(n.branch.vars))); const chosen = (n.branch.cases && Object.prototype.hasOwnProperty.call(n.branch.cases, key)) ? n.branch.cases[key] : n.branch.default; const val = chosen === undefined ? NABLA_SKIP : nablaSubst(chosen, resolved); resolved[id] = val; if (val === NABLA_SKIP) skipped.push(id); continue; }
    if (nablaHasSkip(n.args || {}, resolved)) { resolved[id] = NABLA_SKIP; skipped.push(id); continue; }
    resolved[id] = nablaOp(n.op, nablaSubst(n.args || {}, resolved));
  }
  const outId = dag.output || order[order.length - 1];
  const outVal = resolved[outId];
  return { output: outVal === NABLA_SKIP ? null : outVal, skipped, order };
}

window.SkyCloud = {
  ready,
  visitor: visitorPub,
  get user() { return cur.name ? { name: cur.name, id: cur.pub } : null; },

  /* נתונים חיים משותפים */
  load: (key) => (kv[key] || {}).v,
  all: () => { const o = {}; for (const k in kv) o[k] = kv[k].v; return o; },
  save: async (key, value) => {
    const entry = { v: value, ts: Date.now() };
    merge({ tags: [['d', PRE.kv + key]], content: JSON.stringify(entry), pubkey: cur.pub }, true);
    return publish(await makeEvent([['d', PRE.kv + key], ['p', PROJECT]], JSON.stringify(entry)));
  },
  watch: (key, fn) => { (keyWatchers[key] = keyWatchers[key] || []).push(fn); },
  onChange: (fn) => { globalWatchers.push(fn); },

  /* משתמשים — כמו Firebase Auth, בלי שרת ובלי מסד סיסמאות */
  register: async (name, pass) => {
    await ready;
    const u = await deriveUser(name, pass);
    const taken = usersReg[u.nm];
    if (taken && taken.pub !== u.pub) throw new Error('SkyCloud: username already taken');
    const out = await activate(u.nm, u.bits);
    publish(await makeEvent([['d', PRE.usr + cur.pub], ['p', PROJECT]], JSON.stringify({ name: u.nm, ts: Date.now() })));
    return out;
  },
  login: async (name, pass) => {
    await ready;
    const u = await deriveUser(name, pass);
    const reg = usersReg[u.nm];
    if (!reg) throw new Error('SkyCloud: unknown username (or not yet synced)');
    if (reg.pub !== u.pub) throw new Error('SkyCloud: wrong password');
    return activate(u.nm, u.bits);
  },
  logout: () => { toVisitor(); },
  usersCount: () => Object.keys(usersReg).length,

  /* אחסון פרטי מוצפן לכל משתמש (AES-256-GCM, מפתח נגזר מהסיסמה) */
  userSet: async (key, value) => {
    if (!cur.aes) throw new Error('SkyCloud: login required');
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, cur.aes, enc.encode(JSON.stringify(value))));
    const entry = { iv: hex(iv), ct: b64(ct), ts: Date.now() };
    userData[cur.pub.slice(0, 16) + ':' + key] = entry;
    return publish(await makeEvent([['d', PRE.ud + cur.pub.slice(0, 16) + ':' + key], ['p', PROJECT]], JSON.stringify(entry)));
  },
  userGet: async (key) => {
    if (!cur.aes) throw new Error('SkyCloud: login required');
    const e = userData[cur.pub.slice(0, 16) + ':' + key];
    if (!e) return undefined;
    try {
      const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: unhex(e.iv) }, cur.aes, unb64(e.ct));
      return JSON.parse(dec.decode(pt));
    } catch { return undefined; }
  },

  /* קבצים — העלאה מהדפדפן, URL קבוע באחסון המבוזר */
  upload,

  /* הודעות מוצפנות מקצה-לקצה (Ψ) — אף אחד בדרך לא יכול לקרוא */
  owner: PROJECT,
  sendTo: async (recipientId, value) => {
    await ready;
    const d = PRE.dm + hex(crypto.getRandomValues(new Uint8Array(9)));
    const key = await ecdhKey(recipientId);
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const ct = new Uint8Array(await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv, additionalData: enc.encode('sc-psi:' + d) },
      key, enc.encode(JSON.stringify({ v: value, ts: Date.now() }))));
    const body = new Uint8Array(12 + ct.length);
    body.set(iv, 0); body.set(ct, 12);
    return publish(await makeEvent([['d', d], ['p', PROJECT]], JSON.stringify({ to: recipientId, ct: b64(body), ts: Date.now() })));
  },
  onMessage: (fn) => { dmWatchers.push(fn); },
  messages: () => dms.slice(),
  messagesWith: (id) => dms.filter((m) => m.from === id || m.to === id),

  /* שרת אפליקציה חי (Ω): קריאת API מפורשת דרך הרשת המבוזרת */
  api: apiCall,

  /* חוות השרתים המבוזרת — כל טאב וכל מכשיר הם עובדי-חישוב */
  run: async (task, payload, timeoutMs) => {
    await ready;
    const id = hex(crypto.getRandomValues(new Uint8Array(8)));
    publish(await makeEvent([['d', PRE.job + id], ['p', PROJECT]], JSON.stringify({ task, payload, ts: Date.now(), from: cur.pub })));
    return new Promise((resolve, reject) => {
      const t = setTimeout(() => { delete resWaiters[id]; reject(new Error('SkyCloud: compute timeout for ' + task)); }, timeoutMs || 20000);
      const settle = (r) => { clearTimeout(t); r.ok ? resolve(r.value) : reject(new Error(r.error)); };
      if (results[id]) return settle(results[id]); /* התוצאה כבר הגיעה — לא נתקעים */
      resWaiters[id] = settle;
    });
  },
  serve: (h) => { handlers = Object.assign(handlers || {}, h); },

  /* Ω: הטאב הזה הופך לעובד-שרת — כל מבקר מריץ את ה-backend, ללא מפעיל ייעודי.
     defs: { 'GET /api/ping': fn, 'POST /api/echo': fn }  (fn מקבל req ומחזיר ערך/‏{status,headers,body}) */
  serveApi: (defs) => {
    apiRoutes = apiRoutes || [];
    for (const k in defs) {
      const sp = k.trim().split(/\s+/);
      const method = sp.length > 1 ? sp[0].toUpperCase() : 'GET';
      const path = sp.length > 1 ? sp[1] : sp[0];
      apiRoutes.push({ method, path, fn: defs[k] });
    }
    return window.SkyCloud;
  },
  /* פרסום תמונת-API סטטית מהדפדפן — נענית מהרשת גם ללא אף עובד חי */
  snapshot: async (path, value) => {
    await ready;
    let status = 200, headers = { 'content-type': 'application/json' }, payload = value;
    if (value && typeof value === 'object' && ('status' in value || 'body' in value || 'headers' in value)) { status = value.status || 200; headers = value.headers || headers; payload = value.body; }
    let bytes;
    if (payload instanceof Uint8Array) bytes = payload;
    else if (typeof payload === 'string') bytes = enc.encode(payload);
    else { bytes = enc.encode(JSON.stringify(payload == null ? null : payload)); }
    const entry = { status, headers, body: b64(bytes), ts: Date.now(), snap: true };
    const h = await snapHash(path);
    snaps[h] = entry;
    return publish(await makeEvent([['d', PRE.snp + h], ['p', PROJECT]], JSON.stringify(entry)));
  },

  /**
   * ∇: פתירת אתר-חי מחושב אצל הלקוח — בלי עובד-Ω ובלי מטמון חם לאותו סט.
   * שולף מדיניות ציבורית מהרשת ומריץ את ה-DAG מקומית על inputs כלשהם.
   */
  resolveSite: async (name, opts = {}) => {
    await ready;
    const slug = String(name || 'root').toLowerCase().replace(/[^a-z0-9\\u0590-\\u05ff]+/g, '-').replace(/^-|-$/g, '') || 'root';
    let pol = sitePolicies[slug];
    if (!pol) {
      await new Promise((resolve) => {
        const id = 'pol_' + Math.random().toString(36).slice(2, 8);
        const filter = { kinds: [KIND], authors: [PROJECT], '#d': [PRE.pub + slug], limit: 1 };
        const onMsg = (ev) => {
          try {
            const msg = JSON.parse(ev.data);
            if (msg[0] === 'EVENT' && msg[1] === id) { verifyEvent(msg[2]).then((ok) => { if (ok) merge(msg[2], false); }); }
            if (msg[0] === 'EOSE' && msg[1] === id) { cleanup(); resolve(); }
          } catch {}
        };
        const cleanup = () => { for (const ws of sockets) { try { ws.removeEventListener('message', onMsg); ws.send(JSON.stringify(['CLOSE', id])); } catch {} } };
        for (const ws of sockets) { try { ws.addEventListener('message', onMsg); ws.send(JSON.stringify(['REQ', id, filter])); } catch {} }
        setTimeout(() => { cleanup(); resolve(); }, opts.waitMs || 4500);
      });
      pol = sitePolicies[slug];
    }
    if (!pol || !pol.dag) throw new Error('SkyCloud: site policy not found for "' + slug + '" (not published or not synced yet)');
    const ctx = opts.live ? { hour: new Date().getUTCHours(), dow: new Date().getUTCDay(), minute: new Date().getUTCMinutes(), lang: (navigator.language || 'en').slice(0, 2), user: cur.name || null, visitor: cur.pub.slice(0, 16), __authed: cur.name ? 1 : 0 } : {};
    const inputs = Object.assign({}, ctx, opts.inputs || {});
    if (pol.collapsed && pol.expr) {
      try { return { name: slug, dagId: pol.dagId, output: nablaVM.eval(pol.expr, inputs), source: 'formula-tree', from: 'public-policy' }; } catch {}
    }
    const out = nablaEval(pol.dag, inputs);
    const order = out.order || [];
    const skipped = out.skipped || [];
    const taken = order.filter((id) => skipped.indexOf(id) < 0);
    const pathDigest = hex(await sha256(enc.encode('scpath:' + JSON.stringify({ taken: taken, skipped: skipped })))).slice(0, 32);
    const ctxCanon = JSON.stringify(ctx);
    const ctxDigest = hex(await sha256(enc.encode('sc-ctx:v1:' + ctxCanon))).slice(0, 32);
    const sealed = hex(await sha256(enc.encode('sc-ctxseal:' + ctxDigest + ':' + JSON.stringify(out.output)))).slice(0, 32);
    const bound = { output: out.output, ctxDigest: ctxDigest, sealed: sealed, ctxCanon: ctxCanon };
    const inputsCanon = JSON.stringify(inputs);
    const outputDigest = hex(await sha256(enc.encode('xid:' + JSON.stringify(out.output)))).slice(0, 32);
    const receiptCore = { v: 1, dagId: pol.dagId, inputsCanon: inputsCanon, outputDigest: outputDigest, ts: Date.now(), visitor: cur.pub.slice(0, 16) };
    receiptCore.id = hex(await sha256(enc.encode('screceipt:v1:' + JSON.stringify(receiptCore)))).slice(0, 40);
    let verified = null;
    if (opts.verify) {
      const again = nablaEval(pol.dag, inputs);
      verified = { match: JSON.stringify(again.output) === JSON.stringify(out.output), local: true };
    }
    if (opts.cooperate && out.output !== null && out.output !== undefined) {
      try {
        const body = JSON.stringify({ output: out.output, dagId: pol.dagId, ts: Date.now(), from: cur.pub, warm: true, pathDigest: pathDigest });
        publish(await makeEvent([['d', PRE.pub + 'warm:' + slug + ':' + Date.now().toString(36)], ['p', PROJECT], ['t', 'warm']], body));
        /* Door 5: קרדיט מוניטין טהור ב-webkv */
        const rk = 'reputation:' + cur.pub.slice(0, 16);
        const prev = (kv[rk] && kv[rk].v) || 0;
        const score = (typeof prev === 'number' ? prev : (prev && prev.score) || 0) + 1;
        await window.SkyCloud.save(rk, { score: score, site: slug, ts: Date.now() });
      } catch {}
    }
    if (opts.reportBranch) {
      try {
        publish(await makeEvent([['d', PRE.pub + 'br:' + slug + ':' + pathDigest.slice(0, 12)], ['p', PROJECT], ['t', 'branch']], JSON.stringify({ taken: taken, skipped: skipped, pathDigest: pathDigest, site: slug, author: cur.pub, ts: Date.now() })));
      } catch {}
    }
    return { name: slug, dagId: pol.dagId, output: out.output, source: 'client-eval', skipped: skipped, from: 'public-policy', trace: { taken: taken, skipped: skipped, pathDigest: pathDigest }, bound: bound, receipt: receiptCore, verified: verified, contextOk: true };
  },
  resolveLive: (name, context, opts) => window.SkyCloud.resolveSite(name, Object.assign({}, opts, { live: true, inputs: Object.assign({}, context || {}, (opts && opts.inputs) || {}) })),
  sitePolicies: () => Object.keys(sitePolicies),
  /* Door 6: Φ-dashboard — liveTick + מטריקות ממסרים מ-webkv */
  liveTick: (name, hz, mapInputs, onResult) => {
    const ms = Math.max(200, Math.round(1000 / (hz || 1)));
    let epoch = 0;
    const id = setInterval(async () => {
      epoch++;
      try {
        const inputs = typeof mapInputs === 'function' ? mapInputs(epoch) : Object.assign({ epoch }, mapInputs || {});
        const out = await window.SkyCloud.resolveSite(name, { inputs, live: true });
        const health = window.SkyCloud.load('phi:health');
        if (onResult) onResult(out, epoch, health);
      } catch {}
    }, ms);
    return () => clearInterval(id);
  },
  phiHealth: () => window.SkyCloud.load('phi:health'),
  verifyReceipt: async (receipt, output) => {
    if (!receipt || !receipt.id) return false;
    const core = { v: receipt.v, dagId: receipt.dagId, inputsCanon: receipt.inputsCanon, outputDigest: receipt.outputDigest, ts: receipt.ts, visitor: receipt.visitor };
    const id = hex(await sha256(enc.encode('screceipt:v1:' + JSON.stringify(core)))).slice(0, 40);
    if (id !== receipt.id) return false;
    if (output !== undefined) {
      const od = hex(await sha256(enc.encode('xid:' + JSON.stringify(output)))).slice(0, 32);
      if (od !== receipt.outputDigest) return false;
    }
    return true;
  },
};

/* Door 1+12: אתחול Policy-CDN / טופס #skycloud-live על הכתובת הגלובלית */
(async () => {
  try {
    await ready;
    let meta = null, site = null;
    const el = document.getElementById('skycloud-live');
    if (el && el.getAttribute('data-skysite')) site = el.getAttribute('data-skysite');
    try { const r = await fetch('/skylive.json', { cache: 'no-store' }); if (r.ok) { meta = await r.json(); if (!site) site = meta.site; } } catch {}
    if (!site && !(el && el.getAttribute('data-skylive'))) return;
    site = site || 'pricing';
    const mount = el || (() => { const d = document.createElement('div'); d.id = 'skycloud-live'; document.body.prepend(d); return d; })();
    const outEl = document.getElementById('skycloud-live-out') || mount;
    const phiEl = document.getElementById('skycloud-phi');
    const render = (out) => {
      const pre = document.createElement('pre');
      pre.style.cssText = 'white-space:pre-wrap;font:14px/1.45 ui-monospace,monospace;margin:0';
      pre.textContent = typeof out.output === 'string' ? out.output : JSON.stringify({ output: out.output, source: out.source, verified: out.verified, path: out.trace && out.trace.pathDigest, receipt: out.receipt && out.receipt.id }, null, 2);
      outEl.textContent = '';
      outEl.appendChild(pre);
    };
    const run = async (siteName, inputs, flags) => {
      try { render(await window.SkyCloud.resolveLive(siteName, inputs || {}, flags || {})); }
      catch (e) { outEl.textContent = '∇: ' + (e.message || e); }
    };
    const form = document.getElementById('skycloud-live-form');
    if (form) {
      form.addEventListener('submit', (ev) => {
        ev.preventDefault();
        const fd = new FormData(form);
        const s = String(fd.get('site') || site);
        const n = Number(fd.get('n'));
        const inputs = s === 'access' ? { __authed: n ? 1 : 0 } : (s === 'ranking' ? { score: n } : { n: n });
        run(s, inputs, { cooperate: !!fd.get('cooperate'), verify: !!fd.get('verify'), reportBranch: true });
      });
    }
    await run(site, { n: 25 }, { verify: true });
    /* Door 6: דשבורד Φ — מרענן מטריקות בריאות ממסרים */
    window.SkyCloud.liveTick(site, 0.2, () => ({ n: 10 }), (out, epoch, health) => {
      if (phiEl && health) phiEl.textContent = 'Φ health · relays=' + (health.relaysUp || '?') + ' · φ̄=' + (health.phiAvg || '?') + ' · scenario=' + (health.scenario || '?') + ' · policy=' + (health.activePolicy || '?');
    });
  } catch {}
})();
})();
`;
}

// ─────────────── Blossom — אחסון קבצי האתר ברשת המבוזרת (BUD-01/02) ───────────────

const MIME = {
  html: 'text/html', htm: 'text/html', css: 'text/css', js: 'text/javascript',
  mjs: 'text/javascript', json: 'application/json', txt: 'text/plain',
  md: 'text/markdown', xml: 'application/xml', svg: 'image/svg+xml',
  png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif',
  webp: 'image/webp', ico: 'image/x-icon', pdf: 'application/pdf',
  mp3: 'audio/mpeg', mp4: 'video/mp4', woff: 'font/woff', woff2: 'font/woff2',
};
const mimeOf = (p) => MIME[p.split('.').pop()?.toLowerCase()] ?? 'application/octet-stream';

/** אישור חתום להעלאה (kind 24242) — כך שרתי Blossom מזהים את הבעלים */
function blossomAuth(action, hash) {
  const ev = signEvent(24242, [
    ['t', action],
    ['x', hash],
    ['expiration', String(now() + 600)],
  ], action + ' blob');
  return 'Nostr ' + Buffer.from(JSON.stringify(ev)).toString('base64');
}

/** הרצת פקודת הבנייה של הפרויקט בטרמינל (כמו Vercel/Firebase) */
let buildRunning = null;
let buildQuietUntil = 0; // חלון שבו צופה הקבצים מתעלם משינויים שהבנייה עצמה יצרה
const isBuildQuiet = () => Date.now() < buildQuietUntil;
function runBuild() {
  if (!BUILD_COMMAND) return Promise.resolve();
  if (buildRunning) return buildRunning;
  log(`מריץ בנייה: ${BUILD_COMMAND}`);
  buildQuietUntil = Date.now() + 600_000; // ננעל עד שהבנייה מסתיימת (+מרווח)
  buildRunning = new Promise((resolve) => {
    exec(BUILD_COMMAND, { cwd: ROOT, timeout: 300_000, windowsHide: true }, (err, stdout, stderr) => {
      buildRunning = null;
      buildQuietUntil = Date.now() + 5_000;
      if (err) {
        log('אזהרה: הבנייה נכשלה:', (stderr || err.message).trim().split('\n').slice(-3).join(' | '));
      } else {
        log('הבנייה הושלמה.');
      }
      resolve();
    });
  });
  return buildRunning;
}

/** בדיקה אם הקובץ כבר קיים בשרת (לפי hash) */
async function blossomHas(server, hash) {
  try {
    const res = await fetch(`${server}/${hash}`, { method: 'HEAD', signal: AbortSignal.timeout(8000) });
    return res.ok;
  } catch { return false; }
}

/** העלאת קובץ לכמה שרתי Blossom במקביל; מצליח אם לפחות אחד קלט */
async function blossomUpload(data, contentType) {
  const hash = hex(sha256(data));
  const results = await Promise.allSettled(BLOSSOM_SERVERS.map(async (server) => {
    if (await blossomHas(server, hash)) return server;
    const res = await fetch(`${server}/upload`, {
      method: 'PUT',
      headers: { Authorization: blossomAuth('upload', hash), 'Content-Type': contentType },
      body: data,
      signal: AbortSignal.timeout(45_000),
    });
    if (!res.ok) throw new Error(`${server} -> HTTP ${res.status}`);
    return server;
  }));
  const okServers = results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
  if (okServers.length === 0) {
    const reasons = results.map((r) => r.reason?.message).filter(Boolean).join(' | ');
    throw new Error('אף שרת אחסון לא קיבל את הקובץ: ' + reasons);
  }
  return { hash, servers: okServers };
}

// ═══════════ SkyLattice-Δ: שכבת הריפוי העצמי — אלגברה מעל שדה גלואה GF(2^8) ═══════════
// Δ1: קידוד מחיקה Reed-Solomon עם מטריצת Cauchy — כל קובץ מקבל בלוקי-זוגיות
//     מתמטיים; חלקים שאובדים ברשת משוחזרים מהמתמטיקה עצמה. תכונת המפתח של
//     מטריצת Cauchy: כל תת-מטריצה ריבועית שלה הפיכה, ולכן כל צירוף של k בלוקים
//     (מקוריים או זוגיות) מספיק מתמטית לשחזור מלא — מובטח, לא הסתברותי.
// Δ2: פיצול סוד של שמיר — ה-SYNC_ID מפוצל ל-N רסיסים בעזרת פולינום אקראי
//     מעל GF(2^8); כל K רסיסים משחזרים (אינטרפולציית לגראנז'), K-1 לא חושפים דבר.

const GF_EXP = new Uint8Array(512);
const GF_LOG = new Uint8Array(256);
(() => {
  let x = 1;
  for (let i = 0; i < 255; i++) {
    GF_EXP[i] = x; GF_LOG[x] = i;
    x <<= 1; if (x & 0x100) x ^= 0x11d; // הפולינום הפרימיטיבי של AES/QR
  }
  for (let i = 255; i < 512; i++) GF_EXP[i] = GF_EXP[i - 255];
})();
const gfMul = (a, b) => (a && b) ? GF_EXP[GF_LOG[a] + GF_LOG[b]] : 0;
const gfInv = (a) => GF_EXP[255 - GF_LOG[a]];
const gfDiv = (a, b) => a ? GF_EXP[(GF_LOG[a] - GF_LOG[b] + 255) % 255] : 0;

/** שורת מטריצת Cauchy עבור בלוק זוגיות j מול k בלוקי מקור: 1/(x_j XOR y_i) */
const cauchyRow = (j, k) => {
  const row = new Uint8Array(k);
  for (let i = 0; i < k; i++) row[i] = gfInv((200 + j) ^ i); // x=200..255, y=0..199 — זרים תמיד
  return row;
};

/** Δ1: חישוב m בלוקי זוגיות מ-k בלוקי מקור (כולם באורך זהה) */
function rsEncode(shards, m) {
  const k = shards.length;
  const len = shards[0].length;
  const parity = [];
  for (let j = 0; j < m; j++) {
    const row = cauchyRow(j, k);
    const p = Buffer.alloc(len);
    for (let i = 0; i < k; i++) {
      const c = row[i];
      if (!c) continue;
      const s = shards[i];
      for (let b = 0; b < len; b++) p[b] ^= gfMul(c, s[b]);
    }
    parity.push(p);
  }
  return parity;
}

/**
 * Δ1: שחזור בלוקי מקור חסרים מכל צירוף של k בלוקים זמינים.
 * shards: מערך באורך k+m (מקור ואז זוגיות), null = אבד. מחזיר את k המקוריים או null.
 */
function rsDecode(k, shards, len) {
  const present = [];
  for (let i = 0; i < shards.length && present.length < k; i++) {
    if (shards[i]) present.push(i);
  }
  if (present.length < k) return null; // אבדו יותר בלוקים מכמות הזוגיות — בלתי אפשרי מתמטית
  if (present.every((i) => i < k)) return shards.slice(0, k); // כל המקור קיים

  // בניית מערכת משוואות: שורת יחידה לכל בלוק מקור קיים, שורת Cauchy לכל זוגיות
  const rows = present.map((i) => {
    if (i < k) { const r = new Uint8Array(k); r[i] = 1; return r; }
    return Uint8Array.from(cauchyRow(i - k, k));
  });
  const vals = present.map((i) => Buffer.from(shards[i]));

  // חילוץ גאוס מעל GF(2^8) — חיבור הוא XOR, כפל דרך טבלאות לוגריתם
  for (let col = 0; col < k; col++) {
    let piv = -1;
    for (let r = col; r < k; r++) if (rows[r][col]) { piv = r; break; }
    if (piv < 0) return null;
    [rows[col], rows[piv]] = [rows[piv], rows[col]];
    [vals[col], vals[piv]] = [vals[piv], vals[col]];
    const inv = gfInv(rows[col][col]);
    for (let c = 0; c < k; c++) rows[col][c] = gfMul(rows[col][c], inv);
    for (let b = 0; b < len; b++) vals[col][b] = gfMul(vals[col][b], inv);
    for (let r = 0; r < k; r++) {
      if (r === col || !rows[r][col]) continue;
      const f = rows[r][col];
      for (let c = 0; c < k; c++) rows[r][c] ^= gfMul(f, rows[col][c]);
      for (let b = 0; b < len; b++) vals[r][b] ^= gfMul(f, vals[col][b]);
    }
  }
  return vals;
}

/** Δ2: פיצול סוד ל-n רסיסים, כל k משחזרים (פולינום אקראי מדרגה k-1 לכל בית) */
function shamirSplit(secret, n, k) {
  if (k < 2 || n < k || n > 255) throw new Error('נדרש: 2 <= k <= n <= 255');
  const shares = Array.from({ length: n }, (_, i) => ({ x: i + 1, y: Buffer.alloc(secret.length) }));
  for (let p = 0; p < secret.length; p++) {
    const coef = [secret[p], ...crypto.randomBytes(k - 1)];
    for (const sh of shares) {
      let y = 0, xp = 1;
      for (let c = 0; c < k; c++) { y ^= gfMul(coef[c], xp); xp = gfMul(xp, sh.x); }
      sh.y[p] = y;
    }
  }
  return shares.map((sh) => `SCS${sh.x}-${sh.y.toString('hex').toUpperCase()}`);
}

/** Δ2: שחזור הסוד מרסיסים (אינטרפולציית לגראנז' בנקודה 0) */
function shamirJoin(shareStrings) {
  const shares = shareStrings.map((s) => {
    const m = String(s).trim().match(/^SCS(\d+)-([0-9A-Fa-f]+)$/);
    if (!m) throw new Error('רסיס לא תקין: ' + s);
    return { x: Number(m[1]), y: Buffer.from(m[2], 'hex') };
  });
  const len = shares[0].y.length;
  const secret = Buffer.alloc(len);
  for (let p = 0; p < len; p++) {
    let acc = 0;
    for (let j = 0; j < shares.length; j++) {
      let num = 1, den = 1;
      for (let l = 0; l < shares.length; l++) {
        if (l === j) continue;
        num = gfMul(num, shares[l].x);
        den = gfMul(den, shares[l].x ^ shares[j].x);
      }
      acc ^= gfMul(shares[j].y[p], gfDiv(num, den));
    }
    secret[p] = acc;
  }
  return secret.toString('utf8');
}

const RS_MAX_DATA = 200; // מגבלת בלוקי מקור לקובץ (מטריצת Cauchy ב-GF(2^8))
const rsParityCount = (k) => Math.max(1, Math.ceil(k / 4)); // ‎25%‎ זוגיות, מינימום 1

// ═══════════ SkyLattice-Ψ: ערוצים מוצפנים מקצה-לקצה בין זהויות (ECDH) ═══════════
// "ערבוב הצבעים" של הקריפטוגרפיה: כל צד מערבב את המפתח הסודי שלו עם המפתח
// הציבורי של הצד השני — ושניהם מגיעים לאותו סוד משותף: (dA·PB).x == (dB·PA).x
// מתבונן מהצד (כולל הממסרים) רואה רק את שני ה"צבעים הציבוריים" ולא יכול
// לשחזר את התערובת. מעל הסוד: AES-256-GCM עם כריכת-כתובת (Σ2).

/** גזירת מפתח משותף בין זהות פרטית לציבורית — סימטרי משני הכיוונים */
function ecdhKey(privBig, pubHex) {
  const Ppt = liftX(big(unhex(pubHex)));
  if (!Ppt) throw new Error('Ψ: מפתח ציבורי לא תקין');
  const S = ptMul(Ppt, privBig);
  if (!S) throw new Error('Ψ: נקודת אינסוף');
  return sha256(Buffer.from('skylattice-psi:v1:'), b32(S.x));
}

// פורמט תיל תואם-WebCrypto (iv || ct || tag) — זהה בדפדפן וב-Node
function psiSeal(buf, key32, aad) {
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', key32, iv);
  c.setAAD(Buffer.from('sc-psi:' + aad));
  const ct = Buffer.concat([c.update(buf), c.final()]);
  return Buffer.concat([iv, ct, c.getAuthTag()]).toString('base64');
}
function psiOpen(b64, key32, aad) {
  const raw = Buffer.from(b64, 'base64');
  const d = crypto.createDecipheriv('aes-256-gcm', key32, raw.subarray(0, 12));
  d.setAAD(Buffer.from('sc-psi:' + aad));
  d.setAuthTag(raw.subarray(raw.length - 16));
  return Buffer.concat([d.update(raw.subarray(12, raw.length - 16)), d.final()]);
}

const DEVICE = hex(crypto.randomBytes(6));
const dManifest = () => `sc:${keys.ns}:manifest`;
const dKv = () => `sc:${keys.ns}:kv`;
const dSite = () => `sc:${keys.ns}:site`;
const dChunk = (pathHash, i) => `sc:${keys.ns}:f:${pathHash}:${i}`;
const dWeb = (key) => `scweb:${keys.pubkey.slice(0, 16)}:${key}`;
const dJob = (id) => `scjob:${keys.pubkey.slice(0, 16)}:${id}`;
const dRes = (id) => `scres:${keys.pubkey.slice(0, 16)}:${id}`;
const dApiReq = (id) => `scapi:${keys.pubkey.slice(0, 16)}:${id}`; // Ω: בקשת HTTP מבוזרת
const dApiRes = (id) => `scapx:${keys.pubkey.slice(0, 16)}:${id}`; // Ω: תגובת HTTP מבוזרת
const dSnap = (p) => `scsnp:${keys.pubkey.slice(0, 16)}:${hex(sha256(Buffer.from('snap:' + p))).slice(0, 24)}`; // Ω: תמונת-API סטטית
const pathHash = (rel) => hex(sha256(Buffer.from('p:' + rel))).slice(0, 20);

class Cloud {
  constructor() {
    this.pool = new RelayPool(RELAYS, RELAYS_FALLBACK);
    // SkyLattice-Λ: שכבת-הקוגניציה — מודל-עצמי, שכבות מסתגלות, שודד-ממסרים, נוסחאות
    this.mind = new SelfModel();
    this.brain = new LayerStack([5, 6, 3], { seed: seedFromStr(RUN_SALT) });
    this.relayBandit = new Bandit(RELAYS, { seed: seedFromStr(RUN_SALT + ':bandit') });
    this.formula = new FormulaVM();
    this.scenarios = new ScenarioDetector();
    this.audit = { ok: true, checks: {}, ts: 0 };
    this.state = loadState();
    this.manifest = { files: {}, updatedAt: 0, device: DEVICE };
    this.kv = {};            // key -> {v, ts} — פרטי ומוצפן
    this.kvListeners = [];
    this.webkv = {};         // key -> {v, ts} — ציבורי, משותף עם האתר
    this.webListeners = [];
    this.jobHandlers = {};   // task -> fn — רשת החישוב (SkyLattice)
    this.jobResults = {};    // jobId -> result — מניעת ביצוע כפול
    this.resWaiters = {};    // jobId -> resolve — בקשות שממתינות לתשובה
    this.routes = [];        // Ω: [{method, path/RegExp, fn}] — שרת האפליקציה
    this.apiWaiters = {};    // Ω: reqId -> resolve — בקשות API שממתינות לתשובה
    this.apiDone = {};       // Ω: reqId -> response — מניעת מענה/עיבוד כפול
    this.apiSeen = new Set();// Ω: reqId שכבר טופלו כאן
    this.dms = [];           // הודעות מוצפנות מקצה-לקצה (Ψ)
    this.dmSeen = new Set();
    this.dmListeners = [];
    this.readyPromise = null;
    this.syncing = Promise.resolve();
    this.pendingScan = null;
    this.watcher = null;
    // SkyLattice-ΔB / SkyDB — נתיב-חם מקומי (לא ממסרים)
    this._skydb = new SkyDB({
      dir: path.join(ROOT, '.cloud-skydb'),
      engine: process.env.SC_TEST ? 'memory' : SKYDB_ENGINE,
      author: DEVICE,
      cloud: this,
      nShards: SKYDB_SHARDS,
      peers: [DEVICE],
    });
    this._dbApiEnabled = false;
    this._fullNodeServer = null;
  }

  get db() { return this._skydb; }

  ready() {
    if (!this.readyPromise) this.readyPromise = this._start();
    return this.readyPromise;
  }

  async _start() {
    ensureId();
    deriveKeys();
    this.pool.connectAll();
    // מנסים להתחבר לכמה שיותר ממסרים — כך העדכון מתפזר רחב ברשת
    const n = await this.pool.waitForConnections(6, 15_000);
    if (n === 0) throw new Error('אין חיבור לאף ממסר — בדוק חיבור אינטרנט');
    log(`מחובר ל-${n} ממסרים ברשת | SYNC_ID: ${syncId.slice(0, 14)}…`);
    await this._pullRemote();
    await this._reconcile();
    // גם אם לא היו שינויים בקבצים — לוודא שהאתר החי קיים ומעודכן ברשת
    if (!this.publishedThisRun) {
      await this._publishSite().catch(() => {});
      if (PUBLIC_WEBSITE) await this._publishWebsite().catch((err) => log('אזהרה: עדכון האתר נכשל:', err.message));
    }
    log('סינכרון ראשוני הושלם.');
    log(`כתובת האתר החי של "${PROJECT_NAME}" [${SITE_SLUG}] (זמינה תמיד, גם כשהכול כבוי):`);
    log('  ' + this.address());
    if (PUBLIC_WEBSITE) log('כתובות מראה (אם רשת חוסמת אחת — נסה אחרת): ' + mirrorAddresses().slice(1).join('  ,  '));
    if (CUSTOM_DOMAIN) printDomainSetup();
    // SkyDB: Ω API אוטומטי (נתיב-חם מקומי; לא דורש FULL_NODE)
    if (SKYDB_AUTO || FULL_NODE) {
      try { this.enableDbApi(); } catch (err) { log('SkyDB: enableApi נכשל:', err.message); }
    }
  }

  address() { return globalAddress(); }

  /** משיכת המניפסט וה-KV העדכניים ביותר מהרשת */
  async _pullRemote() {
    // המשיכה הראשונה שקטה (טעינת מצב); משיכות הסקר התקופתי כן מדווחות —
    // כך עדכונים/הודעות/עבודות שהמנוי החי פספס עדיין מפעילים מאזינים.
    const notify = this.firstPullDone === true;
    const webPull = this.pool.query([{ kinds: [KIND_DATA], '#p': [keys.pubkey] }])
      .then((evs) => { for (const ev of evs) this._mergeWebEvent(ev, notify); })
      .catch(() => {});
    const events = await this.pool.query([{ kinds: [KIND_DATA], authors: [keys.pubkey], '#d': [dManifest(), dKv()] }]);
    await webPull;
    for (const ev of events) {
      const d = ev.tags.find((t) => t[0] === 'd')?.[1];
      try {
        const payload = JSON.parse(decrypt(ev.content, d).toString('utf8'));
        if (d === dManifest() && (payload.updatedAt ?? 0) >= (this.manifest.updatedAt ?? 0)) {
          if (this._isRollback(payload)) continue;
          this._acceptManifest(payload);
        } else if (d === dKv()) {
          this._mergeKv(payload, false);
        }
      } catch (err) {
        log('אזהרה: אירוע רשת לא ניתן לפענוח (ייתכן מ-ID אחר):', err.message);
      }
    }
    this.firstPullDone = true;
  }

  // ─────────── SkyLattice-Σ1: שרשרת התחייבויות (Hash-Chain Commitments) ───────────
  // כל מניפסט נושא מונה רץ (seq) ואת ה-hash של המניפסט הקודם (prev) —
  // היסטוריה משורשרת חסינת-זיוף. ממסר שמנסה להגיש גרסה ישנה (rollback) נחשף.

  _isRollback(payload) {
    const knownSeq = this.state.chain?.seq ?? 0;
    if ((payload.seq ?? 0) + 3 < knownSeq) {
      log(`Σ1: נחסם ניסיון rollback — התקבל מניפסט seq=${payload.seq ?? 0} מול שרשרת מקומית seq=${knownSeq}.`);
      return true;
    }
    return false;
  }

  _acceptManifest(payload) {
    this.manifest = payload;
    if ((payload.seq ?? 0) > (this.state.chain?.seq ?? 0)) {
      this.state.chain = { seq: payload.seq ?? 0, hash: hex(sha256(Buffer.from(JSON.stringify(payload)))) };
    }
  }

  /** השוואת מקומי מול ענן — מי עדכני יותר מנצח, לשני הכיוונים */
  async _reconcile() {
    const local = await scanLocal();
    const remote = this.manifest.files ?? {};
    const toUpload = [];
    const toDownload = [];
    const toDeleteLocal = [];
    let manifestChanged = false;

    for (const [rel, info] of local) {
      const r = remote[rel];
      const known = this.state.files[rel];
      if (r?.deleted && r.ts > info.mtime) { toDeleteLocal.push(rel); continue; }
      if (!r || r.deleted) { toUpload.push([rel, info]); continue; }
      if (r.hash === info.hash) {
        this.state.files[rel] = { hash: info.hash, ts: r.ts };
        continue;
      }
      // תוכן שונה — העדכון האחרון מנצח
      const localChanged = !known || known.hash !== info.hash;
      const localTs = localChanged ? info.mtime : (known.ts ?? 0);
      if (localTs > r.ts) toUpload.push([rel, info]);
      else toDownload.push([rel, r]);
    }
    for (const [rel, r] of Object.entries(remote)) {
      if (r.deleted || local.has(rel)) continue;
      const known = this.state.files[rel];
      if (known && known.ts >= r.ts) {
        // הקובץ נמחק מקומית אחרי שסונכרן — לשדר מחיקה לענן
        remote[rel] = { deleted: true, ts: now() * 1000 };
        manifestChanged = true;
        delete this.state.files[rel];
      } else {
        toDownload.push([rel, r]);
      }
    }

    for (const rel of toDeleteLocal) {
      try { await fsp.unlink(path.join(ROOT, rel)); delete this.state.files[rel]; log('נמחק (סונכרן):', rel); } catch {}
    }
    for (const [rel, r] of toDownload) await this._downloadFile(rel, r);
    for (const [rel, info] of toUpload) { await this._uploadFile(rel, info); manifestChanged = true; }
    if (manifestChanged || toUpload.length) await this._publishManifest();
    saveState(this.state);
  }

  async _downloadFile(rel, r) {
    try {
      const ph = pathHash(rel);
      const k = r.chunks;
      const m = r.par ?? 0;
      const dTags = Array.from({ length: k + m }, (_, i) => dChunk(ph, i));
      const parts = new Array(k + m).fill(null);
      for (let i = 0; i < dTags.length; i += 25) {
        const events = await this.pool.query([{ kinds: [KIND_DATA], authors: [keys.pubkey], '#d': dTags.slice(i, i + 25) }]);
        for (const ev of events) {
          // אירוע פגום אחד לא מפיל את שאר החלקים
          try {
            const d = ev.tags.find((t) => t[0] === 'd')?.[1] ?? '';
            const idx = Number(d.split(':').pop());
            // Σ2: הפענוח כבול לכתובת (d) — בלוק שהוזז/הוחלף ייכשל כאן מתמטית
            const payload = JSON.parse(decrypt(ev.content, d).toString('utf8'));
            if (payload.h === r.hash && Number.isInteger(idx)) parts[idx] = Buffer.from(payload.data, 'base64');
          } catch {}
        }
      }
      let dataParts = parts.slice(0, k);
      // Δ1: חלקים חסרים? משוחזרים מתמטית מבלוקי הזוגיות (Reed-Solomon/Cauchy)
      if (dataParts.some((p) => p === null) && m > 0) {
        const missing = dataParts.filter((p) => p === null).length;
        const shards = parts.map((p, i) => {
          if (!p) return null;
          if (i < k && p.length < CHUNK_SIZE) { const s = Buffer.alloc(CHUNK_SIZE); p.copy(s); return s; }
          return p;
        });
        const recovered = rsDecode(k, shards, CHUNK_SIZE);
        if (recovered) {
          const lastLen = r.size - (k - 1) * CHUNK_SIZE;
          dataParts = recovered.map((s, i) => s.subarray(0, i === k - 1 ? lastLen : CHUNK_SIZE));
          log(`Δ1: ${missing} חלקים חסרים של ${rel} שוחזרו מתמטית מבלוקי הזוגיות.`);
        }
      }
      if (dataParts.some((p) => p === null)) { log('אזהרה: חלקים חסרים ברשת עבור', rel, '- ינוסה שוב בסבב הבא'); return; }
      const data = Buffer.concat(dataParts);
      if (hex(sha256(data)) !== r.hash) { log('אזהרה: אימות תוכן נכשל עבור', rel); return; }
      const full = path.join(ROOT, rel);
      await fsp.mkdir(path.dirname(full), { recursive: true });
      this._suppress(rel);
      await fsp.writeFile(full, data);
      this.state.files[rel] = { hash: r.hash, ts: r.ts };
      log('הורד מהענן:', rel, `(${data.length.toLocaleString()} bytes)`);
    } catch (err) {
      log('שגיאה בהורדת', rel + ':', err.message);
    }
  }

  async _uploadFile(rel, info) {
    try {
      const data = await fsp.readFile(path.join(ROOT, rel));
      const hash = hex(sha256(data));
      const ph = pathHash(rel);
      const chunks = Math.max(1, Math.ceil(data.length / CHUNK_SIZE));
      for (let i = 0; i < chunks; i++) {
        const slice = data.subarray(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
        const dTag = dChunk(ph, i);
        const content = encrypt(Buffer.from(JSON.stringify({ h: hash, i, data: slice.toString('base64') })), dTag);
        await this.pool.publish(makeEvent(KIND_DATA, dTag, content));
        if (chunks > 3) await sleep(120); // עדינות מול הממסרים בקבצים גדולים
      }
      // Δ1: בלוקי זוגיות Reed-Solomon — הקובץ ישרוד גם אם חלקים יאבדו ברשת
      let par = 0;
      if (chunks <= RS_MAX_DATA) {
        par = rsParityCount(chunks);
        const shards = [];
        for (let i = 0; i < chunks; i++) {
          const s = Buffer.alloc(CHUNK_SIZE);
          data.copy(s, 0, i * CHUNK_SIZE, Math.min((i + 1) * CHUNK_SIZE, data.length));
          shards.push(s);
        }
        const parity = rsEncode(shards, par);
        for (let j = 0; j < par; j++) {
          const dTag = dChunk(ph, chunks + j);
          const content = encrypt(Buffer.from(JSON.stringify({ h: hash, i: chunks + j, par: 1, data: parity[j].toString('base64') })), dTag);
          await this.pool.publish(makeEvent(KIND_DATA, dTag, content));
          if (par > 3) await sleep(120);
        }
      }
      const ts = info.mtime;
      this.manifest.files[rel] = { hash, size: data.length, mtime: info.mtime, chunks, par, ts };
      this.state.files[rel] = { hash, ts };
      log('הועלה לענן:', rel, `(${data.length.toLocaleString()} bytes, ${chunks} חלקים + ${par} זוגיות)`);
    } catch (err) {
      log('שגיאה בהעלאת', rel + ':', err.message);
    }
  }

  async _publishManifest() {
    this.publishedThisRun = true;
    this.manifest.updatedAt = hlcNow();
    this.manifest.device = DEVICE;
    // Σ1: קישור לשרשרת — מונה רץ + hash המניפסט הקודם
    this.manifest.seq = (this.state.chain?.seq ?? this.manifest.seq ?? 0) + 1;
    this.manifest.prev = this.state.chain?.hash ?? null;
    const json = JSON.stringify(this.manifest);
    this.state.chain = { seq: this.manifest.seq, hash: hex(sha256(Buffer.from(json))) };
    const content = encrypt(Buffer.from(json), dManifest());
    await this.pool.publish(makeEvent(KIND_DATA, dManifest(), content));
    await this._publishSite().catch(() => {});
    if (PUBLIC_WEBSITE) await this._publishWebsite().catch((err) => log('אזהרה: עדכון האתר נכשל:', err.message));
  }

  /**
   * פרסום האתר החי (nsite / NIP-5A): קבצי הפרויקט מועלים לשרתי אחסון
   * מבוזרים, ומניפסט חתום ברשת ממפה כל נתיב לקובץ. שערים ציבוריים מגישים
   * את זה ככתובת חיה — בלי ששום דבר שלך ירוץ ברקע.
   */
  async _publishWebsite() {
    this.state.blobs ??= {};
    // ב---deploy מאמתים מחדש את כל הקבצים מול שרתי האחסון (HEAD זול; מעלה רק חסרים)
    if (this.forceVerifyBlobs) { this.state.blobs = {}; this.forceVerifyBlobs = false; }
    await runBuild();

    // מה מגישים: תיקיית אתר ייעודית (dist/public/... או SITE_DIR) — או כל הפרויקט
    const siteDir = resolveSiteDir();
    let siteFiles; // [webPath, diskPath, size, mtime]
    if (siteDir) {
      siteFiles = [];
      const base = path.join(ROOT, siteDir);
      const walk = async (dir) => {
        let entries = [];
        try { entries = await fsp.readdir(dir, { withFileTypes: true }); } catch {}
        for (const e of entries) {
          if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
          const full = path.join(dir, e.name);
          if (e.isDirectory()) await walk(full);
          else if (e.isFile()) {
            const st = await fsp.stat(full).catch(() => null);
            if (st) siteFiles.push(['/' + path.relative(base, full).replace(/\\/g, '/'), full, st.size, Math.floor(st.mtimeMs)]);
          }
        }
      };
      await walk(base);
      if (!this.siteDirAnnounced) { this.siteDirAnnounced = true; log(`האתר מוגש מתוך "${siteDir}/" (${siteFiles.length} קבצים).`); }
    } else {
      siteFiles = Object.entries(this.manifest.files ?? {})
        .filter(([, f]) => !f.deleted)
        .map(([rel, f]) => ['/' + rel, path.join(ROOT, rel), f.size, f.mtime]);
    }

    const paths = [];   // [webPath, sha256]
    const fileMeta = []; // לרשימת הקבצים בדף הבית האוטומטי
    let uploaded = 0;
    let failed = 0;

    for (const [webPath, diskPath, size, mtime] of siteFiles) {
      try {
        const data = await fsp.readFile(diskPath);
        const hash = hex(sha256(data));
        fileMeta.push([webPath.slice(1), { size, mtime }]);
        if (this.state.blobs[hash]) { paths.push([webPath, hash]); continue; }
        await blossomUpload(data, mimeOf(webPath));
        this.state.blobs[hash] = true;
        paths.push([webPath, hash]);
        uploaded++;
      } catch (err) {
        failed++;
        log('אזהרה: העלאת', webPath, 'לאתר נכשלה:', err.message);
      }
    }
    const files = fileMeta;

    // קבצים שנכשלו — ניסיון חוזר אוטומטי בעוד דקה, עד שהאתר שלם
    if (failed > 0) {
      clearTimeout(this.siteRetryTimer);
      this.siteRetryTimer = setTimeout(() => {
        log(`ניסיון חוזר להעלאת ${failed} קבצים לאתר…`);
        this._publishWebsite().catch((err) => log('אזהרה: עדכון האתר נכשל:', err.message));
      }, 60_000);
      this.siteRetryTimer.unref?.();
    }

    // אם ה-index.html של הפרויקט יושב בתת-תיקייה (public/, www/, dist וכו') —
    // התיקייה ההיא ממופה גם לשורש הכתובת, כך שהאתר האמיתי מוצג ולא רשימת קבצים
    if (!paths.some(([p]) => p === '/index.html')) {
      const candidates = paths
        .filter(([p]) => p.endsWith('/index.html'))
        .map(([p]) => p.slice(0, -'index.html'.length));
      const preferred = ['/public/', '/www/', '/site/', '/web/', '/docs/'].find((d) => candidates.includes(d))
        ?? candidates.sort((a, b) => (a.split('/').length - b.split('/').length) || a.localeCompare(b))[0];
      if (preferred) {
        for (const [p, h] of [...paths]) {
          if (!p.startsWith(preferred)) continue;
          const rootPath = '/' + p.slice(preferred.length);
          if (!paths.some(([existing]) => existing === rootPath)) paths.push([rootPath, h]);
        }
        log(`האתר מוגש מהתיקייה "${preferred.slice(1, -1)}" בשורש הכתובת.`);
      }
    }

    // קובץ הפעלה (.bat/.cmd) שפותח דף HTML בדפדפן — אותו דף הופך לדף הבית
    // של הכתובת הגלובלית, כך שהאתר ברשת מתנהג בדיוק כמו ההפעלה המקומית
    if (!paths.some(([p]) => p === '/index.html')) {
      const batEntry = resolveBatEntry();
      const entry = batEntry && paths.find(([p]) => p === '/' + batEntry.html);
      if (entry) {
        const baseDir = entry[0].slice(0, entry[0].lastIndexOf('/') + 1);
        // כל תיקיית דף הכניסה ממופה לשורש — קישורים יחסיים (css/js/תמונות) ממשיכים לעבוד
        if (baseDir !== '/') {
          for (const [p, h] of [...paths]) {
            if (!p.startsWith(baseDir)) continue;
            const rootPath = '/' + p.slice(baseDir.length);
            if (!paths.some(([existing]) => existing === rootPath)) paths.push([rootPath, h]);
          }
        }
        if (!paths.some(([p]) => p === '/index.html')) paths.push(['/index.html', entry[1]]);
        if (!this.batAnnounced) {
          this.batAnnounced = true;
          log(`זוהה קובץ הפעלה "${batEntry.bat}" — הדף "${batEntry.html}" הוגדר כדף הבית של הכתובת הגלובלית.`);
        }
      }
    }

    // Door 1: מטא-אתר-חי — נשמר ב-state כדי שלא ייעלם ב-republish בלי מדיניות בזיכרון
    const liveSite = this._defaultLiveSite || this.state.liveSite || (this._sitePolicies?.size ? [...this._sitePolicies.keys()][0] : null);
    const livePolicies = this._sitePolicies?.size
      ? [...this._sitePolicies.keys()]
      : (this.state.livePolicies || (liveSite ? [liveSite] : []));
    if (liveSite) {
      try {
        this._defaultLiveSite = liveSite;
        this.state.liveSite = liveSite;
        this.state.livePolicies = livePolicies;
        this.state.policyCdn = !!(this._policyCdn || this.state.policyCdn);
        this.state.policyKit = this._policyKit || this.state.policyKit || null;
        const liveMeta = Buffer.from(JSON.stringify({
          site: liveSite,
          policies: livePolicies,
          kit: this.state.policyKit,
          cdn: !!this.state.policyCdn,
          forms: { pricing: ['n'], access: ['__authed'], ranking: ['score'] },
          live: true, v: 2,
        }), 'utf8');
        const { hash } = await blossomUpload(liveMeta, 'application/json');
        this.state.blobs[hash] = true;
        paths.push(['/skylive.json', hash]);
        saveState(this.state);
      } catch (err) { log('אזהרה: פרסום skylive.json נכשל:', err.message); }
    }

    // מנוע הסינכרון לדפדפן — זמין לכל דף באתר בנתיב /cloud-web.js
    if (!paths.some(([p]) => p === '/cloud-web.js')) {
      try {
        const sdk = Buffer.from(buildWebSdk(), 'utf8');
        const { hash } = await blossomUpload(sdk, 'text/javascript');
        this.state.blobs[hash] = true;
        paths.push(['/cloud-web.js', hash]);
      } catch (err) {
        log('אזהרה: פרסום מנוע הדפדפן נכשל:', err.message);
      }
    }

    // אם עדיין אין index.html — נוצר דף בית אוטומטי + פאנל אתר-חי מחושב (∇)
    if (!paths.some(([p]) => p === '/index.html')) {
      try {
        const html = Buffer.from(this._buildIndexHtml(files), 'utf8');
        const { hash } = await blossomUpload(html, 'text/html');
        this.state.blobs[hash] = true;
        paths.push(['/index.html', hash]);
      } catch (err) {
        log('אזהרה: יצירת דף הבית נכשלה:', err.message);
      }
    }

    if (paths.length === 0) return;
    const aggregate = hex(sha256(Buffer.from(
      paths.map(([p, h]) => `${h} ${p}\n`).sort().join(''),
    )));
    const manifestEv = signEvent(KIND_NSITE, [
      ...paths.map(([p, h]) => ['path', p, h]),
      ['x', aggregate, 'aggregate'],
      ...BLOSSOM_SERVERS.map((s) => ['server', s]),
      ['title', SITE_SLUG],
      ['description', `Live decentralized site "${SITE_SLUG}" (${PROJECT_NAME}) — synced by SkyCloud`],
      ['z', metaPad()], // ריפוד מטמורפי — טביעת-רגל שונה בכל הרצה
    ], '');
    // מניפסט האתר חייב להגיע לכמה שיותר ממסרים — זה מה שהשערים קוראים
    await this.pool.publish(manifestEv, 15_000, 6);
    saveState(this.state);
    if (uploaded > 0 || !this.siteAnnounced) {
      this.siteAnnounced = true;
      log(`האתר החי עודכן ברשת המבוזרת (${paths.length} נתיבים${uploaded ? ', ' + uploaded + ' קבצים חדשים הועלו' : ''}).`);
    }
  }

  /** דף בית אוטומטי — שם הפרויקט, הקבצים והמצב החי */
  _buildIndexHtml(files) {
    const fmtSize = (n) => n >= 1048576 ? (n / 1048576).toFixed(1) + ' MB' : n >= 1024 ? (n / 1024).toFixed(1) + ' KB' : n + ' B';
    const rows = files
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([rel, f]) => `<tr><td><a href="/${rel}">${rel}</a></td><td>${fmtSize(f.size ?? 0)}</td><td>${new Date(f.mtime).toISOString().replace('T', ' ').slice(0, 19)}</td></tr>`)
      .join('\n');
    const updated = new Date().toISOString().replace('T', ' ').slice(0, 19);
    return `<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${PROJECT_NAME}</title>
<style>
  :root { color-scheme: dark; }
  * { box-sizing: border-box; margin: 0; }
  body { font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; background: #0b0e14; color: #e6e9ef; min-height: 100vh; padding: 48px 20px; }
  .wrap { max-width: 860px; margin: 0 auto; }
  .badge { display: inline-block; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: #7dd3a0; border: 1px solid #2a4a38; background: #10231a; border-radius: 999px; padding: 4px 12px; margin-bottom: 18px; }
  h1 { font-size: 34px; margin-bottom: 8px; }
  .sub { color: #8b93a7; margin-bottom: 34px; font-size: 15px; }
  .card { background: #121826; border: 1px solid #1f2937; border-radius: 14px; padding: 22px; margin-bottom: 18px; }
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
  th { text-align: right; color: #8b93a7; font-weight: 600; padding: 8px 10px; border-bottom: 1px solid #1f2937; }
  td { padding: 9px 10px; border-bottom: 1px solid #171e2c; }
  a { color: #7aa2f7; text-decoration: none; }
  a:hover { text-decoration: underline; }
  footer { color: #5c6474; font-size: 12.5px; margin-top: 26px; text-align: center; }
</style>
</head>
<body>
<div class="wrap">
  <span class="badge">אתר חי ברשת מבוזרת</span>
  <h1>${PROJECT_NAME}</h1>
  <p class="sub">עדכון אחרון: ${updated} UTC · ${files.length} קבצים · הדף מתעדכן אוטומטית בכל סינכרון</p>
  <div class="card">
    <h2 style="font-size:16px;margin-bottom:12px;color:#8b93a7">אתר-חי מחושב (∇) — רץ אצל המבקר, בלי Node חי</h2>
    <div id="skycloud-live" data-skysite="${this._defaultLiveSite || 'pricing'}" data-skylive="1">
      <form id="skycloud-live-form" style="display:flex;flex-wrap:wrap;gap:10px;align-items:end;margin-bottom:14px">
        <label style="font-size:13px;color:#8b93a7">מדיניות
          <select name="site" style="display:block;margin-top:4px;background:#0b0e14;color:#e6e9ef;border:1px solid #1f2937;border-radius:8px;padding:6px 10px">
            <option value="pricing">pricing</option>
            <option value="access">access</option>
            <option value="ranking">ranking</option>
          </select>
        </label>
        <label style="font-size:13px;color:#8b93a7">n / score / __authed
          <input name="n" type="number" value="25" style="display:block;margin-top:4px;background:#0b0e14;color:#e6e9ef;border:1px solid #1f2937;border-radius:8px;padding:6px 10px;width:120px">
        </label>
        <button type="submit" style="background:#1f6feb;color:#fff;border:0;border-radius:8px;padding:8px 14px;cursor:pointer">חשב (resolveLive)</button>
        <label style="font-size:12px;color:#8b93a7;display:flex;gap:6px;align-items:center"><input type="checkbox" name="cooperate"> cooperate (חימום+קרדיט)</label>
        <label style="font-size:12px;color:#8b93a7;display:flex;gap:6px;align-items:center"><input type="checkbox" name="verify" checked> אימות ∇</label>
      </form>
      <div id="skycloud-live-out">טוען מדיניות מהרשת…</div>
      <div id="skycloud-phi" style="margin-top:12px;font-size:12px;color:#8b93a7"></div>
    </div>
  </div>
  <div class="card">
    <table>
      <thead><tr><th>קובץ</th><th>גודל</th><th>עודכן</th></tr></thead>
      <tbody>${rows || '<tr><td colspan="3">(אין קבצים עדיין)</td></tr>'}</tbody>
    </table>
  </div>
  <footer>מוגש מרשת מבוזרת (Nostr + Blossom) — אין שרת מרכזי, האתר חי גם כשכל המכשירים כבויים.</footer>
</div>
<script src="/cloud-web.js"></script>
</body>
</html>`;
  }

  /** פרופיל + רשימת ממסרים — כדי ששערי הרשת ידעו לאתר את העמוד */
  async _publishIdentity() {
    const profile = {
      pubkey: keys.pubkey,
      created_at: now(),
      kind: 0,
      tags: [],
      content: JSON.stringify({
        name: 'SkyCloud ' + syncId.slice(3, 11),
        about: 'Private cloud sync node. Data is end-to-end encrypted.',
      }),
    };
    profile.id = hex(sha256(Buffer.from(JSON.stringify([0, profile.pubkey, profile.created_at, profile.kind, profile.tags, profile.content]))));
    profile.sig = hex(schnorrSign(unhex(profile.id), keys.priv));
    const relayList = {
      pubkey: keys.pubkey,
      created_at: now(),
      kind: 10002,
      tags: RELAYS.slice(0, 6).map((r) => ['r', r]),
      content: '',
    };
    relayList.id = hex(sha256(Buffer.from(JSON.stringify([0, relayList.pubkey, relayList.created_at, relayList.kind, relayList.tags, relayList.content]))));
    relayList.sig = hex(schnorrSign(unhex(relayList.id), keys.priv));
    // רשימת שרתי האחסון (BUD-03) — כדי ששערי האתר ידעו מאיפה למשוך את הקבצים
    const serverList = signEvent(KIND_BLOSSOM_LIST, BLOSSOM_SERVERS.map((s) => ['server', s]), '');
    await this.pool.publish(profile).catch(() => {});
    await this.pool.publish(relayList).catch(() => {});
    await this.pool.publish(serverList).catch(() => {});
  }

  /** עמוד הסטטוס הציבורי — זמין בכתובת הגלובלית גם כשהכול כבוי */
  async _publishSite() {
    if (!this.identityPublished) {
      this.identityPublished = true;
      await this._publishIdentity().catch(() => {});
    }
    const files = Object.entries(this.manifest.files ?? {}).filter(([, f]) => !f.deleted);
    const totalSize = files.reduce((s, [, f]) => s + (f.size ?? 0), 0);
    const rows = files
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([rel, f]) => `| \`${rel}\` | ${(f.size ?? 0).toLocaleString()} B | ${new Date(f.mtime).toISOString().replace('T', ' ').slice(0, 19)} |`)
      .join('\n');
    // באנגלית בכוונה — חלק משערי הרשת לא מציגים עברית נכון
    const md = [
      `# SkyCloud Project — ${syncId.slice(0, 14)}...`,
      '',
      `**Last update:** ${new Date().toISOString().replace('T', ' ').slice(0, 19)} UTC`,
      `**Synced files:** ${files.length} | **Total size:** ${totalSize.toLocaleString()} bytes`,
      '',
      '| File | Size | Updated |',
      '|---|---|---|',
      rows || '| _(no files yet)_ | | |',
      '',
      '_File contents are end-to-end encrypted and stored across the network. This page shows live status only._',
    ].join('\n');
    const ev = makeEvent(KIND_SITE, dSite(), md, [
      ['title', 'SkyCloud Project Status'],
      ['published_at', String(now())],
    ]);
    await this.pool.publish(ev);
  }

  // ─────────────── KV — ה-API לאפליקציות (ללא UI) ───────────────

  _mergeKv(remoteKv, notify = true) {
    for (const [k, entry] of Object.entries(remoteKv ?? {})) {
      hlcObserve(entry?.ts); // Θ: מקדם את השעון הלוגי מעבר לכל זמן שנצפה
      const cur = this.kv[k];
      if (!cur || entry.ts > cur.ts) {
        this.kv[k] = entry;
        if (notify) for (const fn of this.kvListeners) { try { fn(k, entry.v); } catch {} }
      }
    }
  }

  async set(key, value) {
    await this.ready();
    this.kv[key] = { v: value, ts: hlcNow() };
    const content = encrypt(Buffer.from(JSON.stringify(this.kv)), dKv());
    await this.pool.publish(makeEvent(KIND_DATA, dKv(), content));
  }

  get(key) { return this.kv[key]?.v; }
  getAll() { return Object.fromEntries(Object.entries(this.kv).map(([k, e]) => [k, e.v])); }
  onChange(fn) { this.kvListeners.push(fn); }

  // ─────────────── webkv — נתונים חיים משותפים עם האתר הציבורי ───────────────
  // ערוץ ציבורי (לא מוצפן): גם האתר בדפדפן וגם המכשירים שלך קוראים
  // וכותבים בו — זה מה שמאפשר מערכת חיה עם משתמשים ישירות מהכתובת.

  _mergeWebEvent(ev, notify = true) {
    const d = ev.tags.find((t) => t[0] === 'd')?.[1] ?? '';
    const p16 = keys.pubkey.slice(0, 16);
    // ניתוב רשת החישוב: תוצאות עבודה ובקשות חישוב חדשות
    if (d.startsWith(`scres:${p16}:`)) {
      try {
        const res = JSON.parse(ev.content);
        if (typeof res?.ts === 'number') this._onResult(d.slice(`scres:${p16}:`.length), res);
      } catch {}
      return;
    }
    if (d.startsWith(`scjob:${p16}:`)) {
      try {
        const job = JSON.parse(ev.content);
        if (typeof job?.ts === 'number' && notify) this._onJob(d.slice(`scjob:${p16}:`.length), job).catch(() => {});
      } catch {}
      return;
    }
    // Ω: תגובת API חוזרת למבקש
    if (d.startsWith(`scapx:${p16}:`)) {
      try { const res = JSON.parse(ev.content); if (typeof res?.ts === 'number') this._onApiRes(d.slice(`scapx:${p16}:`.length), res); } catch {}
      return;
    }
    // Ω: בקשת API — עובד-שרת מריץ ומחזיר תגובה
    if (d.startsWith(`scapi:${p16}:`)) {
      try { const req = JSON.parse(ev.content); if (typeof req?.ts === 'number' && notify) this._onApiReq(d.slice(`scapi:${p16}:`.length), req).catch(() => {}); } catch {}
      return;
    }
    if (d.startsWith(`scdm:${p16}:`)) { this._onDm(ev, d, notify); return; }
    const prefix = `scweb:${p16}:`;
    if (!d.startsWith(prefix)) return;
    const key = d.slice(prefix.length);
    try {
      const entry = JSON.parse(ev.content);
      if (typeof entry?.ts !== 'number') return;
      hlcObserve(entry.ts);
      const cur = this.webkv[key];
      if (!cur || entry.ts > cur.ts) {
        this.webkv[key] = entry;
        if (notify) for (const fn of this.webListeners) { try { fn(key, entry.v, ev.pubkey); } catch {} }
      }
    } catch {}
  }

  async webSet(key, value) {
    await this.ready();
    const entry = { v: value, ts: hlcNow() };
    this.webkv[key] = entry;
    await this.pool.publish(signEvent(KIND_DATA, [['d', dWeb(key)], ['p', keys.pubkey]], JSON.stringify(entry)));
  }

  webGet(key) { return this.webkv[key]?.v; }
  webGetAll() { return Object.fromEntries(Object.entries(this.webkv).map(([k, e]) => [k, e.v])); }
  onWebChange(fn) { this.webListeners.push(fn); }

  // ─────────────── SkyLattice — רשת חישוב מבוזרת של הפרויקט ───────────────
  // כל מכשיר שמריץ serve() וכל טאב דפדפן פתוח של האתר הופכים לעובד-חישוב.
  // בקשה (run) משוגרת לרשת; העובדים בוחרים מבצע יחיד בבחירה דטרמיניסטית
  // (Rendezvous Hashing — לכל עובד ציון sha256(worker+jobId), הנמוך רץ ראשון)
  // כך אין ביצוע כפול ואין צורך במתאם מרכזי. התוצאה חתומה וחוזרת למבקש.

  /** רישום המכשיר הזה כעובד-חישוב: serve({ taskName: async (payload) => result }) */
  serve(handlers) {
    Object.assign(this.jobHandlers, handlers);
    // קבלת עבודות דורשת מנוי חי — מובטח גם אם serve נקרא לפני ready
    this.ready().then(() => this.startLive()).catch(() => {});
    log(`עובד-חישוב פעיל: ${Object.keys(this.jobHandlers).join(', ')}`);
  }

  /** שיגור עבודת חישוב לרשת — כל עובד זמין (Node או דפדפן) יענה */
  async run(task, payload, timeoutMs = 20_000) {
    await this.ready();
    this.startLive(); // קבלת התוצאה דורשת מנוי חי
    const id = hex(crypto.randomBytes(8));
    const entry = { task, payload, ts: Date.now(), from: keys.pubkey };
    await this.pool.publish(signEvent(KIND_DATA, [['d', dJob(id)], ['p', keys.pubkey]], JSON.stringify(entry)));
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        delete this.resWaiters[id];
        reject(new Error(`compute timeout: אף עובד לא ענה למשימה "${task}"`));
      }, timeoutMs);
      const settle = (res) => {
        clearTimeout(timer);
        res.ok ? resolve(res.value) : reject(new Error(res.error));
      };
      // התוצאה אולי כבר הגיעה לפני שנרשמנו (עובד מהיר במיוחד) — לא נתקעים
      if (this.jobResults[id]) return settle(this.jobResults[id]);
      this.resWaiters[id] = settle;
    });
  }

  async _onJob(id, job) {
    const fn = this.jobHandlers[job.task];
    if (!fn || this.jobResults[id]) return;
    if (Date.now() - job.ts > 60_000) return; // עבודה ישנה — לא רלוונטית
    // בחירת מבצע דטרמיניסטית: ציון לפי sha256; עובדי Node מקבלים עדיפות קלה
    const rank = Number(BigInt('0x' + hex(sha256(Buffer.from(DEVICE + ':' + id))).slice(0, 8)) % 700n);
    await sleep(150 + rank);
    if (this.jobResults[id]) return; // עובד אחר כבר ענה
    let res;
    try { res = { ok: true, value: await fn(job.payload, job) }; }
    catch (err) { res = { ok: false, error: err.message }; }
    res.ts = Date.now();
    res.worker = DEVICE;
    this.jobResults[id] = res;
    await this.pool.publish(signEvent(KIND_DATA, [['d', dRes(id)], ['p', keys.pubkey]], JSON.stringify(res))).catch(() => {});
  }

  _onResult(id, res) {
    if (!this.jobResults[id] || res.ts > this.jobResults[id].ts) this.jobResults[id] = res;
    const waiter = this.resWaiters[id];
    if (waiter) { delete this.resWaiters[id]; waiter(res); }
  }

  // ─────────── SkyLattice-Ω: שרת אפליקציה חי על הכתובת הגלובלית ───────────
  // הכתובת הגלובלית מגישה מעטפת סטטית; כאן חי "צד השרת". כל מכשיר Node
  // שרשם route()/bridge() הוא עובד-שרת. הדפדפן שולח בקשות HTTP דרך הרשת
  // המבוזרת, עובד יחיד נבחר דטרמיניסטית (Rendezvous), מריץ ומחזיר תגובה
  // חתומה. מערכת מלאה (Next.js/Express/סוכני AI) חיה בכתובת — לא סטטית.

  /** רישום נתיב שרת: route('GET', '/api/users', async (req) => ({ status, body })) */
  route(method, pathPattern, fn, opts = {}) {
    this.routes.push({ method: String(method).toUpperCase(), path: pathPattern, fn, static: !!opts.static });
    this.ready().then(() => this.startLive()).then(() => { if (opts.static && typeof pathPattern === 'string') this._snapshotRoute(this.routes[this.routes.length - 1]); }).catch(() => {});
    if (!this.httpAnnounced) { this.httpAnnounced = true; log('Ω: שרת אפליקציה פעיל — הכתובת הגלובלית מריצה צד-שרת חי דרך הרשת.'); }
    return this;
  }

  /**
   * פרסום "תמונת-API" סטטית לרשת: תשובה מחושבת-מראש שמוגשת מהממסרים
   * גם כשאין אף עובד-שרת חי בעולם — backend ללא-מפעיל לקריאות מבוססות-נתונים.
   */
  async snapshot(pathName, value, opts = {}) {
    await this.ready();
    this.startLive();
    let status = 200, headers = { 'content-type': 'application/json' }, payload = value;
    if (value && typeof value === 'object' && ('status' in value || 'body' in value || 'headers' in value)) {
      status = value.status ?? 200; headers = value.headers ?? headers; payload = value.body;
    }
    let bodyBuf;
    if (Buffer.isBuffer(payload)) bodyBuf = payload;
    else if (typeof payload === 'string') bodyBuf = Buffer.from(payload, 'utf8');
    else { bodyBuf = Buffer.from(JSON.stringify(payload ?? null), 'utf8'); headers['content-type'] ??= 'application/json'; }
    const res = { status, headers, body: bodyBuf.toString('base64'), enc: 'base64', ts: hlcNow(), snap: true };
    if (res.body.length > API_INLINE_MAX) {
      try { const { hash, servers } = await blossomUpload(bodyBuf, headers['content-type'] || 'application/octet-stream'); res.blob = `${servers[0]}/${hash}`; delete res.body; } catch {}
    }
    await this.pool.publish(signEvent(KIND_DATA, [['d', dSnap(pathName)], ['p', keys.pubkey], ['z', metaPad()]], JSON.stringify(res)), 12_000, 2);
    return this;
  }

  /** מריץ נתיב סטטי מול בקשת-GET סינתטית ומפרסם את התוצאה כתמונת-API */
  async _snapshotRoute(r) {
    try {
      const out = await this._serveRequest({ method: 'GET', path: r.path, headers: {}, body: '' });
      const body = out.blob ? Buffer.from(await (await fetch(out.blob)).arrayBuffer()) : Buffer.from(out.body || '', 'base64');
      await this.snapshot(r.path, { status: out.status, headers: out.headers, body });
    } catch (err) { log(`Ω: כשל בצילום נתיב סטטי ${r.path}: ${err.message}`); }
  }

  /** גשר לשרת מקומי אמיתי (Next.js/Express/כל שפה) שרץ אצלך — שרת Node *מלא* */
  bridge(url) {
    this.bridgeUrl = String(url).replace(/\/$/, '');
    this.ready().then(() => this.startLive()).catch(() => {});
    log(`Ω: גשר שרת פעיל — בקשות API מהכתובת הגלובלית מגושרות אל ${this.bridgeUrl}`);
    return this;
  }

  _matchRoute(method, pathname) {
    for (const r of this.routes) {
      if (r.method !== method && r.method !== 'ALL') continue;
      if (typeof r.path === 'string') { if (r.path === pathname) return { r, params: {} }; }
      else if (r.path instanceof RegExp) { const m = pathname.match(r.path); if (m) return { r, params: m.groups ?? {} }; }
    }
    return null;
  }

  /** ביצוע בקשה מקומית: גשר → שרת אמיתי, אחרת נתיב רשום */
  async _serveRequest(req) {
    const pathname = req.path.split('?')[0];
    // נתיבים מקומיים (SkyDB / route) קודמים לגשר — אחרת bridge בולע /api/db/*
    const hit = this._matchRoute(req.method, pathname);
    if (hit) {
      let out;
      try {
        const body = req.body ? Buffer.from(req.body, 'base64') : Buffer.alloc(0);
        out = await hit.r.fn({ method: req.method, path: req.path, headers: req.headers || {}, params: hit.params, body, json: () => JSON.parse(body.toString('utf8') || 'null'), text: () => body.toString('utf8') });
      } catch (err) {
        return { status: 500, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ error: err.message })).toString('base64'), enc: 'base64' };
      }
      let status = 200, headers = { 'content-type': 'application/json' }, payload;
      if (out && typeof out === 'object' && ('status' in out || 'body' in out || 'headers' in out)) {
        status = out.status ?? 200;
        headers = out.headers ?? headers;
        payload = out.body;
      } else payload = out;
      let bodyBuf;
      if (Buffer.isBuffer(payload)) bodyBuf = payload;
      else if (typeof payload === 'string') bodyBuf = Buffer.from(payload, 'utf8');
      else { bodyBuf = Buffer.from(JSON.stringify(payload ?? null), 'utf8'); headers['content-type'] ??= 'application/json'; }
      return { status, headers, body: bodyBuf.toString('base64'), enc: 'base64' };
    }
    if (this.bridgeUrl) {
      const r = await fetch(this.bridgeUrl + req.path, {
        method: req.method,
        headers: req.headers || {},
        body: ['GET', 'HEAD'].includes(req.method) ? undefined : Buffer.from(req.body || '', 'base64'),
        signal: AbortSignal.timeout(25_000),
      });
      const buf = Buffer.from(await r.arrayBuffer());
      const headers = {}; r.headers.forEach((v, k) => { headers[k] = v; });
      return { status: r.status, headers, body: buf.toString('base64'), enc: 'base64' };
    }
    return { status: 404, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ error: 'no route', path: req.path })).toString('base64'), enc: 'base64' };
  }

  async _onApiReq(id, req) {
    if (!this.routes.length && !this.bridgeUrl) return; // המכשיר הזה אינו עובד-שרת
    if (this.apiSeen.has(id)) return;
    if (Date.now() - (req.ts || 0) > 30_000) return; // בקשה ישנה
    // בחירת מבצע דטרמיניסטית (Rendezvous) — עובד יחיד עונה, בלי כפילות
    const rank = Number(BigInt('0x' + hex(sha256(Buffer.from(DEVICE + ':' + id))).slice(0, 8)) % 500n);
    await sleep(60 + rank);
    if (this.apiSeen.has(id)) return;
    this.apiSeen.add(id);
    if (this.apiSeen.size > 4000) this.apiSeen.clear();
    let res;
    try { res = await this._serveRequest(req); }
    catch (err) { res = { status: 502, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ error: err.message })).toString('base64'), enc: 'base64' }; }
    res.ts = Date.now(); res.worker = DEVICE;
    // תגובה גדולה עוברת דרך Blossom (URL) במקום inline — כמו CDN לתגובות כבדות
    if (res.body && res.body.length > API_INLINE_MAX) {
      try {
        const { hash, servers } = await blossomUpload(Buffer.from(res.body, 'base64'), res.headers?.['content-type'] || 'application/octet-stream');
        res.blob = `${servers[0]}/${hash}`; delete res.body;
      } catch {}
    }
    await this.pool.publish(signEvent(KIND_DATA, [['d', dApiRes(id)], ['p', keys.pubkey]], JSON.stringify(res))).catch(() => {});
  }

  _onApiRes(id, res) {
    if (this.apiDone[id]) return;
    this.apiDone[id] = res;
    const w = this.apiWaiters[id];
    if (w) { delete this.apiWaiters[id]; w(res); }
  }

  /** קריאת API מצד Node (למשל לבדיקות/הטמעה): מחזירה {status, headers, body:Buffer} */
  async fetchApi(pathOrReq, init = {}) {
    await this.ready();
    this.startLive();
    const req = typeof pathOrReq === 'string'
      ? { method: (init.method || 'GET').toUpperCase(), path: pathOrReq, headers: init.headers || {}, body: init.body ? Buffer.from(init.body).toString('base64') : '', ts: Date.now(), from: keys.pubkey }
      : pathOrReq;
    const id = hex(crypto.randomBytes(8));
    await this.pool.publish(signEvent(KIND_DATA, [['d', dApiReq(id)], ['p', keys.pubkey]], JSON.stringify(req)));
    const res = await new Promise((resolve, reject) => {
      const timer = setTimeout(() => { delete this.apiWaiters[id]; reject(new Error(`Ω: אף עובד-שרת לא ענה ל-${req.path}`)); }, init.timeoutMs || 25_000);
      const settle = (r) => { clearTimeout(timer); resolve(r); };
      if (this.apiDone[id]) return settle(this.apiDone[id]);
      this.apiWaiters[id] = settle;
    });
    let body = res.body ? Buffer.from(res.body, 'base64') : Buffer.alloc(0);
    if (res.blob) body = Buffer.from(await (await fetch(res.blob)).arrayBuffer());
    return { status: res.status, headers: res.headers || {}, body };
  }

  // ─────────────── SkyLattice-Ψ: הודעות מוצפנות מקצה-לקצה ───────────────
  // המכשיר הזה (זהות הפרויקט) יכול להתכתב מוצפן עם כל משתמש/מבקר של האתר,
  // והם איתו — אף גורם בדרך (כולל הממסרים) לא יכול לפענח.

  _onDm(ev, d, notify = true) {
    try {
      const env = JSON.parse(ev.content);
      if (typeof env?.ts !== 'number' || typeof env?.ct !== 'string' || typeof env?.to !== 'string') return;
      const mine = env.to === keys.pubkey;
      const fromMe = ev.pubkey === keys.pubkey;
      if (!mine && !fromMe) return; // הודעה בין צדדים אחרים — לא ניתנת לפענוח אצלנו ממילא
      if (this.dmSeen.has(d)) return;
      const other = mine ? ev.pubkey : env.to;
      const key = ecdhKey(keys.priv, other === keys.pubkey ? keys.pubkey : other);
      const payload = JSON.parse(psiOpen(env.ct, key, d).toString('utf8'));
      this.dmSeen.add(d);
      const msg = { from: ev.pubkey, to: env.to, value: payload.v, ts: payload.ts };
      this.dms.push(msg);
      this.dms.sort((a, b) => a.ts - b.ts);
      if (notify) for (const fn of this.dmListeners) { try { fn(msg); } catch {} }
    } catch {} // פענוח נכשל = לא מיועד לנו / חבלה — נזרק בשקט
  }

  /** שליחת הודעה מוצפנת מקצה-לקצה לזהות אחרת (משתמש/מבקר/מכשיר) */
  async sendTo(recipientPub, value) {
    await this.ready();
    this.startLive();
    const d = `scdm:${keys.pubkey.slice(0, 16)}:${hex(crypto.randomBytes(9))}`;
    const key = ecdhKey(keys.priv, recipientPub);
    const ct = psiSeal(Buffer.from(JSON.stringify({ v: value, ts: Date.now() })), key, d);
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['p', keys.pubkey]], JSON.stringify({ to: recipientPub, ct, ts: Date.now() })));
  }

  onMessage(fn) { this.dmListeners.push(fn); }
  messages() { return [...this.dms]; }

  /** העלאת קובץ ציבורי לרשת האחסון המבוזרת — מקבל URL קבוע שחי לתמיד */
  async upload(input, type) {
    await this.ready();
    let data, mime = type;
    if (Buffer.isBuffer(input)) data = input;
    else {
      const p = path.isAbsolute(String(input)) ? String(input) : path.join(ROOT, String(input));
      if (fs.existsSync(p) && fs.statSync(p).isFile()) { data = await fsp.readFile(p); mime ??= mimeOf(p); }
      else data = Buffer.from(String(input), 'utf8');
    }
    mime ??= 'application/octet-stream';
    const { hash, servers } = await blossomUpload(data, mime);
    return { hash, url: `${servers[0]}/${hash}`, urls: servers.map((s) => `${s}/${hash}`), size: data.length, type: mime };
  }

  // ─────────── ייבוא מערכת שלמה מ-ZIP → פריקה + זיהוי + הגשה ───────────
  /** פורק ZIP לתיקיית הפרויקט, מזהה תיקיית פרסום/בנייה, ומסנכרן לכתובת */
  async importZip(zipPath, opts = {}) {
    const abs = path.isAbsolute(zipPath) ? zipPath : path.join(ROOT, zipPath);
    const buf = await fsp.readFile(abs);
    const entries = unzip(buf);
    // אם כל הקבצים תחת תיקיית-שורש אחת (כמו myapp/…) — פורקים בלעדיה
    const tops = new Set(entries.map((e) => e.name.split('/')[0]));
    const strip = tops.size === 1 && entries.every((e) => e.name.includes('/'));
    const destDir = opts.dir ? path.join(ROOT, opts.dir) : ROOT;
    let written = 0;
    for (const e of entries) {
      const rel = strip ? e.name.split('/').slice(1).join('/') : e.name;
      if (!rel || /(^|\/)node_modules\//.test(rel)) continue; // תלויות יותקנו מחדש
      const full = path.join(destDir, rel);
      if (!full.startsWith(destDir)) continue; // הגנת path-traversal (Zip Slip)
      await fsp.mkdir(path.dirname(full), { recursive: true });
      await fsp.writeFile(full, e.data);
      written++;
    }
    log(`ייבוא ZIP: נפרקו ${written} קבצים${strip ? ' (הוסרה תיקיית-שורש)' : ''}.`);
    // זיהוי חכם: package.json עם build/start → הצעת שילוב מלא
    try {
      const pkg = JSON.parse(await fsp.readFile(path.join(destDir, 'package.json'), 'utf8'));
      const scripts = pkg.scripts || {};
      if (scripts.build) log(`זוהתה פקודת בנייה: "npm run build" — הגדר BUILD_COMMAND='npm run build' כדי לבנות לפני פרסום.`);
      if (scripts.start || scripts.dev) log(`זוהה שרת: הרץ אותו מקומית (npm start) והגדר LOCAL_BRIDGE='http://127.0.0.1:<port>' — הכתובת הגלובלית תריץ אותו מלא (לא סטטי).`);
    } catch {}
    const sd = resolveSiteDir();
    if (sd) log(`תיקיית הגשה שזוהתה: ${sd}/`);
    return { files: written };
  }

  // ─────────── Θ: ריפוי-עצמי — רענון שכפול + אימות שלמות אוטומטי ───────────
  // מחזורי: (1) מרענן את המניפסט והזהות ברשת כדי לשמור שכפול רחב (מונע
  // דעיכת TTL בממסרים), (2) מאמת מחדש שקבצי האתר עדיין קיימים בשרתי האחסון
  // ומעלה מחדש חסרים. תיקון-עצמי בלי מגע יד אדם, מונע-נתונים.
  async _healSweep() {
    try {
      await this._publishSite().catch(() => {});
      if (Object.keys(this.manifest.files ?? {}).length) {
        await this.pool.publish(makeEvent(KIND_DATA, dManifest(), encrypt(Buffer.from(JSON.stringify(this.manifest)), dManifest())), 12_000, 4).catch(() => {});
      }
      if (PUBLIC_WEBSITE) { this.forceVerifyBlobs = true; await this._publishWebsite().catch(() => {}); }
      // Ω: רענון תמונות-API סטטיות — נשמרות זמינות לשירות ללא-מפעיל
      for (const r of this.routes) if (r.static && typeof r.path === 'string') await this._snapshotRoute(r).catch(() => {});
      const rep = this.pool.healthReport();
      const weak = rep.filter((r) => r.phi > 6).length;
      if (weak) log(`Θ: ריפוי-עצמי — ${rep.length} ממסרים פעילים, ${weak} חשודים; שכפול המידע רועֶן ברשת.`);
      // Λ: פעימת מודל-עצמי + אודיט קריפטוגרפי + פעולה אוטונומית + זיהוי-תרחישים
      await this._selfAudit();
      const pulse = this._pulse(rep);
      const scAct = pulse.scenario?.action;
      if (pulse.action === 'hop' || scAct === 'hop' || scAct === 'swap_degraded' || scAct === 'heal') this.pool.autoHop();
      // Door 3: ScenarioDetector מחליף מדיניות Roam (policy:degraded ↔ policy:full)
      if (pulse.scenario?.activePolicy) {
        const want = pulse.scenario.activePolicy; // 'policy:full' | 'policy:degraded'
        if (this._activePolicyRoam !== want) {
          await this.publishName('active-policy', want).catch(() => {});
          this._activePolicyRoam = want;
          // Door 15: אפליקציה מודעת-תרחיש — מצביע ל-UI/מדיניות
          const app = scenarioAppSelect(pulse.scenario, this._scenarioAppMap || { relay_degraded: 'degraded', high_load: 'degraded', logic_fork: 'degraded', default: 'full' });
          if (app) await this.publishName('app:ui', 'policy:' + app).catch(() => {});
        }
      }
      if (scAct === 'publish_warm' || scAct === 'client_prefer') {
        const site = this._defaultLiveSite || (this._sitePolicies && [...this._sitePolicies.keys()][0]);
        if (site) this.smartWarm(site, { k: 3 }).catch(() => {});
      }
      if (scAct === 'republish_policy' && this._sitePolicies) {
        for (const [slug, pol] of this._sitePolicies) await this._publishPublicPolicy(slug, pol).catch(() => {});
      }
      // S8: checkpoint SkyDB → רשת + Φ dashboard
      let dbTip = null;
      try { dbTip = await this.publishDbCheckpoint(); } catch {}
      try {
        await this.webSet('phi:health', {
          relaysUp: rep.length, phiAvg: +(rep.reduce((s, r) => s + r.phi, 0) / (rep.length || 1)).toFixed(3),
          vitality: pulse.vitality, scenario: pulse.scenario?.scenario, action: scAct,
          activePolicy: pulse.scenario?.activePolicy, auditOk: !!this.audit.ok, ts: Date.now(),
          db: this._skydb?.health?.() || null, dbTipDigest: dbTip?.digest || null,
        });
      } catch {}
      log(`Λ: דופק — חיוניות ${(pulse.vitality * 100).toFixed(0)}% · תרחיש ${pulse.scenario?.scenario || '?'} · אודיט ${this.audit.ok ? 'תקין' : 'נכשל!'} · ${pulse.action}`);
      this._shareBrain().catch(() => {});
      // Ξ: נוסחאות-קבע — נפתרות ומתפרסמות מחדש כדי להישאר "חמות" במטמון-הרשת
      if (Array.isArray(this._xiStanding)) for (const p of this._xiStanding) { try { await this._publishJobResult(xiJobId(p), xiSolve(p)); } catch {} }
      // Roam: נדידת-שכפול של רשומות-השם — השם נשאר קריא והתוכן נע במרחב
      await this._roamSweep().catch(() => {});
      // ∇: רענון מדיניות ציבורית — מבקרים תמיד יכולים להריץ אצל עצמם
      if (this._sitePolicies) for (const [slug, pol] of this._sitePolicies) await this._publishPublicPolicy(slug, pol).catch(() => {});
    } catch (err) { log('Θ: מחזור ריפוי נכשל (ינוסה שוב):', err.message); }
  }

  /** Λ: מזין טלמטריה למודל-העצמי ומאמן את השכבות המסתגלות מהתוצאה בפועל */
  _pulse(rep = this.pool.healthReport()) {
    const relaysUp = rep.length;
    const phiAvg = relaysUp ? rep.reduce((s, r) => s + r.phi, 0) / relaysUp : 99;
    const scores = rep.map((r) => Math.max(0, r.score ?? 0));
    const snapFresh = clamp01(1 - (Date.now() - (this._lastSnapTs || Date.now())) / (2 * HEAL_SWEEP_MS));
    const backlog = this.pendingScan ? 1 : 0;
    const pulse = this.mind.observe({ relaysUp, phiAvg, scores, snapFresh, backlog });
    // השכבות לומדות לנבא "מצב בריא" מהטלמטריה — משקולות שמעדכנות את עצמן
    const feat = [clamp01(relaysUp / 8), clamp01(1 - phiAvg / 10), clamp01(SelfModel.entropy(scores)), snapFresh, this.audit.ok ? 1 : 0];
    this.brain.forward(feat);
    const healthyTarget = pulse.vitality > 0.6 ? 1 : 0;
    this.brain.feedback([healthyTarget, pulse.action === 'steady' ? 1 : 0, this.audit.ok ? 1 : 0]);
    // השודד לומד איזה ממסר משתלם (תגמול = ציון-בריאות מנורמל)
    rep.forEach((r) => { const i = this.relayBandit.arms.indexOf(r.url); if (i >= 0) this.relayBandit.reward(i, clamp01((r.score ?? 0))); });
    // Door 15: זיהוי-תרחישים — פותח פעולות/דלתות לפי מצב המנוע
    const dbHealth = this._skydb?.health?.() || {};
    const scenario = this.scenarios.observe({
      relaysUp, phiAvg, scores, vitality: pulse.vitality, backlog,
      nCached: (this._usageWarm || []).length, nComputed: 0,
      hasIdentity: false,
      policyAgeMs: this._sitePolicies?.size ? 0 : 9e9,
      dbLagMs: dbHealth.lagMs || 0, // S8
    });
    // S13: logic_fork → pin DB tip
    if (scenario.scenario === 'logic_fork') this.pinDbOnFork().catch(() => {});
    pulse.scenario = scenario;
    pulse.db = dbHealth;
    return pulse;
  }

  /**
   * Λ: אודיט-עצמי קריפטוגרפי ("פיצוח-עצמי לאבטחה") — המנוע תוקף/בודק את עצמו:
   * חתימה↔אימות, זיהוי-זיוף, הצפנה↔פענוח כבול-כתובת, ושחזור Reed-Solomon.
   * אם אחד נכשל — משהו נשבר, והמודל-העצמי יסמן ירידת חיוניות.
   */
  async _selfAudit() {
    const checks = {};
    try {
      const ev = signEvent(KIND_DATA, [['d', 'audit:probe']], 'skylattice-self-audit');
      checks.signVerify = verifyEvent(ev) === true;
      const forged = { ...ev, content: ev.content + 'x' };
      checks.forgeryRejected = verifyEvent(forged) === false;
      const pt = Buffer.from('audit-' + hex(crypto.randomBytes(8)));
      const d = 'audit:aead';
      checks.aead = decrypt(encrypt(pt, d), d).equals(pt);
      const K = 4, LEN = 64;
      const orig = Array.from({ length: K }, () => crypto.randomBytes(LEN));
      const parity = rsEncode(orig, 2);
      const lossy = [...orig, ...parity].map((s, i) => (i === 1 ? null : s)); // מאבדים בלוק אחד
      const rec = rsDecode(K, lossy, LEN);
      checks.reedSolomon = rec != null && rec.every((s, i) => s.equals(orig[i]));
      checks.formula = this.formula.eval('clamp(2^3 + max(1,4)*2, 0, 100)') === 16;
    } catch (err) { checks.error = err.message; }
    const ok = Object.values(checks).every((v) => v === true);
    this.audit = { ok, checks, ts: Date.now() };
    if (!ok) log('Λ: אזהרה — אודיט-עצמי מצא כשל בשלמות:', JSON.stringify(checks));
    return this.audit;
  }

  /** Λ: שיתוף פדרטיבי של משקולות המוח — כך הן "חיות" ומתכנסות בין peers */
  async _shareBrain() {
    try {
      const flat = this.brain.weights().flat().flat();
      await this.pool.publish(signEvent(KIND_DATA, [['d', `scbrn:${keys.pubkey.slice(0, 16)}`], ['p', keys.pubkey], ['z', metaPad()]], JSON.stringify({ w: flat, ts: hlcNow(), from: DEVICE })), 8_000, 1).catch(() => {});
    } catch {}
  }

  /** תמונת בריאות הרשת — לניטור עצמי (Θ) */
  health() { return this.pool.healthReport(); }

  /** Λ: "המנוע מדבר עם עצמו" — דופק, אודיט, דירוג ממסרים נלמד, ומצב המוח */
  cognition() {
    const rep = this.pool.healthReport();
    const pulse = this._pulse(rep);
    return {
      pulse,
      scenario: pulse.scenario || this.scenarios.last,
      audit: this.audit,
      relayRanking: this.relayBandit.ranking().slice(0, 5),
      brainOut: this.brain.acts ? this.brain.acts[this.brain.acts.length - 1] : null,
      relaysUp: rep.length,
      policies: this.listPolicies(),
      liveSite: this._defaultLiveSite || null,
    };
  }

  /** Λ: הרצת נוסחה בזמן אמת כנגד הטלמטריה החיה (policy-as-data) */
  evalFormula(formula, extraVars = {}) {
    const rep = this.pool.healthReport();
    const phiAvg = rep.length ? rep.reduce((s, r) => s + r.phi, 0) / rep.length : 99;
    const vars = { relaysUp: rep.length, phiAvg, vitality: this.mind.last().vitality ?? 1, entropy: SelfModel.entropy(rep.map((r) => r.score ?? 0)), audit: this.audit.ok ? 1 : 0, ...extraVars };
    return this.formula.eval(formula, vars);
  }

  // ══════════ SkyLattice-Ξ: רשת-החישוב הדטרמיניסטית (חישוב-חופשי-מהרשת) ══════════
  // compute() מחזיר תוצאה של חישוב טהור: קודם בודק אם היא כבר *במטמון הרשת*
  // (מוגשת עם אפס פותרים חיים!); אם לא — פותר מקומית, מפרסם לצמיתות, ומחזיר.
  // אמינות: דטרמיניזם ⇒ ניתן לאמת ע"י חישוב-חוזר; opts.quorum דורש k מסכימים.
  get _xiKeys() { return (this.__xiKeys ??= deviceKeypair()); }

  async _xiCollect(jobId, waitMs = 3500) {
    await this.ready();
    const out = new Map(); // author → {author,digest,result}
    return new Promise((resolve) => {
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], '#j': [jobId], '#t': ['xi-res'], limit: 64 }],
        (ev) => {
          try {
            if (verifyEvent(ev) !== true) return;
            const dec = JSON.parse(decrypt(ev.content, jobId).toString());
            if (dec.jobId !== jobId || !dec.digest) return;
            out.set(ev.pubkey, { author: ev.pubkey, digest: dec.digest, result: dec.result });
          } catch {}
        },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve([...out.values()]); }, waitMs);
    });
  }

  async _publishJobResult(jobId, solved) {
    const content = encrypt(Buffer.from(JSON.stringify({ jobId, digest: solved.digest, result: solved.result, ts: hlcNow() })), jobId);
    const ev = signEventAs(KIND_DATA, [['d', `scxi:${jobId}:${this._xiKeys.pubkey.slice(0, 16)}`], ['j', jobId], ['t', 'xi-res'], ['z', metaPad()]], content, this._xiKeys);
    await this.pool.publish(ev, 10_000, 1).catch(() => {});
  }

  /** בקשת חישוב טהור — קודם מטמון-רשת (אפס-מפעיל), אחרת פתירה+פרסום מקומיים */
  async compute(program, opts = {}) {
    await this.ready(); this.startLive();
    const quorum = Math.max(1, opts.quorum || 1);
    const jobId = xiJobId(program);
    // 1) מטמון-רשת: אולי כבר נפתר אי-פעם — מוגש גם עם אפס פותרים חיים
    let results = await this._xiCollect(jobId, opts.waitMs || (quorum > 1 ? 4000 : 2500));
    let q = xiQuorum(results, quorum);
    if (q.ok) {
      if (opts.verify !== false && XI_OPS[program.op]) { const local = xiSolve(program); if (local.digest !== q.digest) throw new Error('Ξ: תוצאת-הרשת לא תואמת חישוב-חוזר — נדחתה'); }
      return { jobId, result: q.result, digest: q.digest, agree: q.agree, source: 'network-cache' };
    }
    // 2) לא נמצא/לא-הגיע לקוורום: פותרים מקומית, מפרסמים לצמיתות, ומחזירים
    const solved = xiSolve(program);
    await this._publishJobResult(jobId, solved);
    // גם משגרים בקשה — כדי שפותרים נוספים יאמתו וישלימו קוורום (אם התבקש)
    await this.pool.publish(signEventAs(KIND_DATA, [['d', `scxiq:${jobId}:${this._xiKeys.pubkey.slice(0, 16)}`], ['j', jobId], ['t', 'xi-req'], ['z', metaPad()]], encrypt(Buffer.from(JSON.stringify({ program })), jobId), this._xiKeys), 8000, 1).catch(() => {});
    if (quorum > 1) {
      results = await this._xiCollect(jobId, opts.waitMs || 6000);
      results.push({ author: this._xiKeys.pubkey, digest: solved.digest, result: solved.result });
      q = xiQuorum(results, quorum);
      if (q.ok) return { jobId, result: q.result, digest: q.digest, agree: q.agree, source: 'network-quorum' };
    }
    return { jobId, result: solved.result, digest: solved.digest, agree: 1, source: 'local-solve' };
  }

  /** פותר עצמאי: מאזין לבקשות-חישוב פתוחות ברשת, פותר, ומפרסם — "הרשת הקטנה" */
  serveCompute(opts = {}) {
    this.ready().then(() => this.startLive()).then(() => {
      this._xiServing = true;
      this.pool.subscribe(
        [{ kinds: [KIND_DATA], '#t': ['xi-req'], since: now() - 5 }],
        async (ev) => {
          try {
            if (verifyEvent(ev) !== true) return;
            const jTag = (ev.tags.find((t) => t[0] === 'j') || [])[1];
            if (!jTag || this._xiSeen?.has(jTag)) return;
            (this._xiSeen ??= new Set()).add(jTag);
            const { program } = JSON.parse(decrypt(ev.content, jTag).toString());
            if (!program || xiJobId(program) !== jTag) return; // הבקשה חייבת להתאים ל-jobId
            const solved = xiSolve(program);
            await this._publishJobResult(jTag, solved);
            log(`Ξ: נפתרה משימת-חישוב ${jTag.slice(0, 12)}… (op=${program.op}) ופורסמה לרשת.`);
          } catch (err) { /* משימה לא-חוקית/לא-נתמכת — מתעלמים בשקט */ }
        },
        () => {},
      );
      log('Ξ: פותר-עצמאי פעיל — מאזין למשימות-חישוב ברשת המבוזרת.');
    }).catch(() => {});
    // נוסחאות-קבע: המנוע משגר ופותר אותן לבד ושומר אותן "חמות" ברשת
    if (Array.isArray(opts.standing)) this._xiStanding = opts.standing.map((p) => (typeof p === 'string' ? { op: 'formula', args: { expr: p } } : p));
  }

  /**
   * הרצת צנרת-חישוב (DAG): כל צומת עובר דרך compute() — לכן פלטי-הביניים
   * נשמרים במטמון-הרשת ומצטברים; הרצה חוזרת של אותה צנרת = פגיעות-מטמון.
   * הצנרת כולה ממוענת-לפי-תוכן, כך שגם הפלט הסופי נשמר תחת dagId אחד.
   */
  async runDag(dag, opts = {}) {
    await this.ready(); this.startLive();
    // הזרקת קלטי-זמן-ריצה: כל id ב-inputs מוחלף בצומת-ערך → jobId שונה = מטמון-לפי-קלט
    if (opts.inputs && typeof opts.inputs === 'object') {
      dag = { ...dag, nodes: { ...dag.nodes } };
      for (const k of Object.keys(opts.inputs)) dag.nodes[k] = { value: opts.inputs[k] };
    }
    const dagId = xiDagId(dag);
    if (opts.cache !== false) {
      const cached = await this._xiCollect(dagId, opts.waitMs || 2200);
      const q = xiQuorum(cached, 1);
      if (q.ok) return { dagId, output: q.result, source: 'network-cache', results: null };
    }
    const ev = await xiDagEval(dag, (program) => this.compute(program, { quorum: opts.quorum || 1, waitMs: opts.stepWaitMs || 1500 }).then((r) => ({ result: r.result, digest: r.digest, source: r.source })));
    // שמירת הפלט הסופי של הצנרת כארטיפקט-מטמון אחד תחת dagId
    await this._publishJobResult(dagId, { result: ev.output, digest: ev.dagDigest }).catch(() => {});
    const nComputed = Object.values(ev.sources).filter((s) => s === 'local-solve' || s === 'computed').length;
    const nCached = Object.values(ev.sources).filter((s) => s === 'network-cache' || s === 'memo').length;
    return { dagId, output: ev.output, outputSkipped: ev.outputSkipped, results: ev.results, jobIds: ev.jobIds, sources: ev.sources, skipped: ev.skipped, nComputed, nCached, dagDigest: ev.dagDigest, order: ev.order, source: 'computed' };
  }

  // ═══════════ "אתר-חי מחושב": שם → צנרת-DAG מותנית → תוכן דינמי אפס-מפעיל ═══════════
  _dDag(dagId) { return `scdag:${keys.ns}:${dagId}`; }

  async _publishDagSpec(dag) {
    const dagId = xiDagId(dag);
    const d = this._dDag(dagId);
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['t', 'dagspec'], ['z', metaPad()]], encrypt(Buffer.from(JSON.stringify(dag)), d)), 12_000, 2);
    return dagId;
  }
  async _fetchDagSpec(dagId, waitMs = 3500) {
    await this.ready();
    const d = this._dDag(dagId);
    return new Promise((resolve) => {
      let found = null;
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [keys.pubkey], '#d': [d], limit: 1 }],
        (ev) => { try { if (verifyEvent(ev) === true) found = JSON.parse(decrypt(ev.content, d).toString()); } catch {} },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve(found); }, waitMs);
    });
  }

  _dPub(slug) { return `scpub:${keys.pubkey.slice(0, 16)}:${slug}`; }

  /** מפרסם מדיניות ציבורית חתומה (לא מוצפנת) — כל מבקר יכול להריץ אצל עצמו */
  async _publishPublicPolicy(slug, policy) {
    const d = this._dPub(slug);
    // תוכן גלוי במכוון: התוכנית היא הציבורית; החתימה (Σ3) מונעת זיוף.
    // תג p מאפשר ל-SDK בדפדפן לקבל את המדיניות במנוי #p הקיים.
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['p', keys.pubkey], ['t', 'sitepol'], ['z', metaPad()]], JSON.stringify({ ...policy, name: slug, ts: now() })), 12_000, 2);
    return d;
  }

  async _fetchPublicPolicy(slug, waitMs = 4000) {
    await this.ready();
    const d = this._dPub(slug);
    return new Promise((resolve) => {
      let found = null;
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [keys.pubkey], '#d': [d], limit: 1 }],
        (ev) => { try { if (verifyEvent(ev) === true) found = JSON.parse(ev.content); } catch {} },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve(found); }, waitMs);
    });
  }

  /**
   * מפרסם אתר-חי מחושב + מדיניות ∇ ציבורית (+גרסת Roam).
   * מבקרים מריצים כל סט-קלטים אצל עצמם — בלי עובד ובלי מטמון חם לאותו סט.
   */
  async publishSite(name, dag) {
    await this.ready(); this.startLive();
    const slug = slugify(name) || 'root';
    const policy = xiCollapseFull(dag);
    const dagId = await this._publishDagSpec(dag);
    await this._publishPublicPolicy(slug, policy);
    await this.publishName(name, 'dag:' + dagId); // Door 8: seq מונוטוני על מצביע המדיניות
    await this.publishName('policy:' + slug, 'pol:' + (policy.dagId || dagId));
    (this._sitePolicies ??= new Map()).set(slug, policy);
    (this._defaultLiveSite ??= slug); // Door 1: אתר ברירת-מחדל לכתובת הגלובלית
    log(`∇ אתר-חי: "${slug}" → dag:${dagId} light=${policy.light} collapsed=${!!policy.collapsed}. מדיניות ציבורית+גרסת-Roam.`);
    return { name: slug, dagId, light: policy.light, ops: policy.ops, collapsed: !!policy.collapsed, expr: policy.expr };
  }

  /** Door 2: ספריית מדיניות — מפרסם מדיניות בשם ייעודי (pricing/ranking/access/…) */
  async publishPolicy(name, dagOrDsl) {
    const dag = typeof dagOrDsl === 'string' ? compilePolicyDSL(dagOrDsl).dag : dagOrDsl;
    return this.publishSite(name, dag);
  }
  /** Door 1: מפרסם ערכת pricing/access/ranking + מכוון skylive להרכבה */
  async publishPolicyKit(opts = {}) {
    const out = {};
    for (const [name, dsl] of Object.entries(POLICY_KIT)) {
      out[name] = await this.publishPolicy(name, dsl);
    }
    // מדיניות מלאה/מצומצמת להחלפת-תרחיש (Door 3)
    out.full = await this.publishPolicy('full', POLICY_KIT.pricing);
    out.degraded = await this.publishPolicy('degraded', [
      'policy degraded',
      'in n = 1',
      'out = formula 0',
      'output out',
    ].join('\n'));
    await this.publishName('policy:full', 'dag:' + out.full.dagId);
    await this.publishName('policy:degraded', 'dag:' + out.degraded.dagId);
    await this.publishName('active-policy', 'policy:full');
    this.enablePolicyCdn(opts.site || 'pricing');
    this._policyKit = Object.keys(POLICY_KIT);
    this.state.liveSite = this._defaultLiveSite;
    this.state.livePolicies = [...(this._sitePolicies?.keys() || [])];
    this.state.policyKit = this._policyKit;
    this.state.policyCdn = true;
    saveState(this.state);
    log(`Policy-kit פורסם: ${this._policyKit.join(', ')}. Policy-CDN skylive→${this._defaultLiveSite}`);
    return { kit: this._policyKit, site: this._defaultLiveSite, cdn: true, ...out };
  }
  async getPolicy(name, opts = {}) {
    // Door 17: cross-project — name כמו "npub1abc/pricing" או {pubkey, name}
    if (opts.pubkey || (typeof name === 'string' && name.includes('/'))) {
      const pub = opts.pubkey || name.split('/')[0];
      const slug = slugify(opts.pubkey ? name : name.split('/').slice(1).join('/')) || 'root';
      return this._fetchPublicPolicyFrom(pub, slug, opts.waitMs);
    }
    const slug = slugify(name) || 'root';
    return (await this._fetchPublicPolicy(slug)) || this._sitePolicies?.get(slug) || null;
  }
  async _fetchPublicPolicyFrom(pubkey, slug, waitMs = 4000) {
    const d = `scpub:${String(pubkey).slice(0, 16)}:${slug}`;
    await this.ready();
    return new Promise((resolve) => {
      let found = null;
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [pubkey], '#d': [d], limit: 1 }],
        (ev) => { try { if (verifyEvent(ev) === true) found = JSON.parse(ev.content); } catch {} },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve(found); }, waitMs);
    });
  }
  listPolicies() { return [...(this._sitePolicies?.keys() || [])]; }

  /**
   * Door 2: מרכיב מדיניות מרובות בזמן ריצה — מפה {alias: policyName} → inputs מורחבים
   * או DAG מעטפת שמפעיל nested.
   */
  async composePolicies(map, opts = {}) {
    const nodes = {};
    for (const [alias, polName] of Object.entries(map || {})) {
      nodes[alias] = { value: { $policy: polName, inputs: (opts.inputs && opts.inputs[alias]) || opts.inputs || {} } };
    }
    const outId = opts.output || Object.keys(nodes)[0];
    const wrap = { nodes: { ...nodes, __out: { coalesce: Object.keys(nodes).map((a) => ({ $ref: a })) } }, output: opts.coalesce ? '__out' : outId };
    if (!opts.coalesce) wrap.output = outId;
    return this.resolveSiteDag(wrap, opts);
  }

  /** פותר DAG מקומי (עם הרחבת nested) בלי שם Roam */
  async resolveSiteDag(dag, opts = {}) {
    // Door 7: meta-policy router — inputs.__route בוחר מדיניות מהמפה
    if (opts.routeMap && opts.inputs && opts.inputs.__route != null) {
      const routed = metaRoute(opts.inputs.__route, opts.routeMap, (n) => this._sitePolicies?.get(slugify(n)));
      dag = routed.dag;
      opts = { ...opts, name: routed.name };
    }
    const fetchPol = async (n) => this.getPolicy(n, opts.foreign || {});
    const expanded = await xiExpandNested(dag, fetchPol);
    let inputs = { ...(opts.inputs || {}) };
    if (opts.identity !== false) {
      inputs = { ...inputs, __user: opts.user || null, __visitor: opts.visitor || keys?.pubkey?.slice(0, 16) || null, __authed: opts.user ? 1 : 0 };
    }
    if (opts.live) inputs = { ...liveContext(opts.context || {}), ...inputs };
    const ev = await xiClientEval(expanded, inputs);
    const trace = branchTrace(ev);
    const bound = contextBind(ev.output, opts.live || opts.bindContext ? liveContext(opts.context || inputs) : { _: 0 });
    const receipt = makeReceiptBody(ev.dagId || xiDagId(expanded), inputs, ev.output, { visitor: inputs.__visitor });
    let verified = null;
    if (opts.verify) verified = await this.verifyClientResult(expanded, inputs, ev);
    if (opts.cooperate) {
      try {
        await this.runDag(expanded, { inputs, cache: true });
        await this.creditWarmer(inputs.__visitor || keys.pubkey, opts.name || 'site'); // Door 5 reputation
      } catch {}
    }
    if (opts.reportBranch) await this.reportBranch(opts.name || 'site', trace).catch(() => {});
    (this._usageWarm ??= []).push({ inputs, ts: Date.now(), name: opts.name || null });
    if (this._usageWarm.length > 400) this._usageWarm.shift();
    return { ...ev, verified, trace, bound, receipt, source: 'client-eval', contextOk: contextCheck(bound, opts.live || opts.bindContext ? liveContext(opts.context || inputs) : { _: 0 }) };
  }

  /** Door 5: קרדיט מוניטין למבקר-מחמם (Roam reputation:<pub16>) */
  async creditWarmer(who, site) {
    const id = String(who || 'anon').slice(0, 16);
    const name = 'reputation:' + id;
    const cur = await this.resolveName(name, { waitMs: 1500 });
    let score = 1;
    try { if (cur.target && String(cur.target).startsWith('rep:')) score = (parseInt(String(cur.target).slice(4), 10) || 0) + 1; } catch {}
    await this.publishName(name, 'rep:' + score);
    return { who: id, site, score };
  }

  /** Door 13: דיווח ענף שנבחר — לקונצנזוס-חישוב */
  async reportBranch(site, trace) {
    const d = `scbr:${keys.pubkey.slice(0, 16)}:${slugify(site)}:${trace.pathDigest}`;
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['p', keys.pubkey], ['t', 'branch'], ['z', metaPad()]], JSON.stringify({ ...trace, site: slugify(site), author: keys.pubkey, ts: Date.now() })), 8_000, 1);
    (this._branchReports ??= []).push({ ...trace, author: keys.pubkey, site: slugify(site) });
    const cons = consensusBranches(this._branchReports.filter((r) => r.site === slugify(site)), 2);
    if (cons.fork) this.scenarios.observe({ ...this.scenarios.last, logicFork: true, relaysUp: 6, phiAvg: 0 });
    return cons;
  }

  /**
   * פותר אתר-חי: כברירת-מחדל במצב ∇ (client) — שולף מדיניות/מפרט ומריץ *מקומית*
   * על הקלטים. תומך ב-nested, זהות, live-context, verify, cooperate.
   */
  async resolveSite(name, opts = {}) {
    const slug = slugify(name) || 'root';
    const mode = opts.mode || 'client';
    let dag = null, dagId = null, from = null, pol = null;

    // Door 8: time-travel — resolve לפי seq היסטורי של Roam (audit/rollback יזום)
    if (opts.seq != null && opts.seq !== '') {
      const hist = await this.resolveName(name, { seq: opts.seq, waitMs: opts.waitMs });
      if (hist.target && String(hist.target).startsWith('dag:')) {
        dagId = String(hist.target).slice(4);
        dag = await this._fetchDagSpec(dagId, opts.waitMs || 3500);
        from = 'historical-roam';
      } else if (hist.target && String(hist.target).startsWith('policy:')) {
        const ps = slugify(String(hist.target).slice(7)) || 'root';
        pol = await this._fetchPublicPolicy(ps, opts.waitMs || 4000);
        if (pol?.dag) { dag = pol.dag; dagId = pol.dagId || xiDagId(dag); from = 'historical-policy'; }
      }
    }

    if (!dag) {
      pol = await this._fetchPublicPolicy(slug, opts.waitMs || 4000);
      if (pol && pol.dag) { dag = pol.dag; dagId = pol.dagId || xiDagId(dag); from = 'public-policy'; }
    }
    if (!dag) {
      const { target } = await this.resolveName(name, opts);
      if (!target || !String(target).startsWith('dag:')) return { name: slug, output: undefined, reason: 'no-dag' };
      dagId = String(target).slice(4);
      dag = await this._fetchDagSpec(dagId, opts.waitMs || 3500);
      from = 'encrypted-spec';
    }
    if (!dag) return { name: slug, dagId, output: undefined, reason: 'spec-missing' };

    // Door 9 (formula): אם התמוטט לנוסחה — מעריכים ישירות (מהיר בדפדפן/Node)
    if (mode === 'client' && pol?.collapsed && pol.expr && opts.inputs) {
      try {
        const vars = { ...(opts.inputs || {}) };
        const output = this.formula.eval(pol.expr, vars);
        const bound = contextBind(output, opts.live || opts.bindContext ? liveContext(opts.context || vars) : { _: 0 });
        return { name: slug, dagId, output, source: 'formula-tree', from, collapsed: true, bound, contextOk: contextCheck(bound, opts.live || opts.bindContext ? liveContext(opts.context || vars) : { _: 0 }) };
      } catch { /* נופל להרצת-DAG מלאה */ }
    }

    if (mode === 'network') {
      const run = await this.runDag(dag, { inputs: opts.inputs, quorum: opts.quorum });
      return { name: slug, dagId, output: run.output, source: run.source, nComputed: run.nComputed, nCached: run.nCached, skipped: run.skipped, from };
    }

    const ev = await this.resolveSiteDag(dag, { ...opts, name: slug });
    let signedReceipt = null;
    if (opts.signReceipt && ev.receipt) signedReceipt = await this.signReceipt(ev.receipt);
    return {
      name: slug, dagId: ev.dagId || dagId, output: ev.output, source: ev.source,
      skipped: ev.skipped, sources: ev.sources, from, verified: ev.verified,
      trace: ev.trace, bound: ev.bound, receipt: signedReceipt || ev.receipt, contextOk: ev.contextOk,
    };
  }

  /** Door 14: כתובת חיה לפי נוסחה/הקשר — כל טעינה עם hour/lang/user */
  async resolveLive(name, context = {}, opts = {}) {
    return this.resolveSite(name, { ...opts, live: true, context, inputs: { ...(opts.inputs || {}), ...liveContext(context) } });
  }

  /** Door 6: אימות תוצאת-לקוח מול קוורום/digest רשת */
  async verifyClientResult(dag, inputs, ev) {
    try {
      const local = await xiClientEval(dag, inputs);
      const match = local.dagDigest === ev.dagDigest || JSON.stringify(local.output) === JSON.stringify(ev.output);
      let quorum = null;
      if (ev.dagId) {
        const results = await this._xiCollect(ev.dagId, 2000);
        quorum = xiQuorum(results.map((r) => ({ ...r, author: r.author })), 1);
      }
      return { match, localDigest: local.dagDigest, quorumOk: !!(quorum && quorum.ok), agree: quorum?.agree || 0 };
    } catch (err) { return { match: false, error: err.message }; }
  }

  /** Door 5: חימום-כיסוי חכם — bandit/שימוש בוחרים אילו סטי-קלטים לחמם */
  async smartWarm(name, opts = {}) {
    const slug = slugify(name) || 'root';
    const k = opts.k || 5;
    const usage = (this._usageWarm || []).filter((u) => !u.name || u.name === slug);
    // מועמדים: מהשימוש האחרון + סריג בסיסי
    const cand = [];
    for (const u of usage.slice(-40)) cand.push(u.inputs || {});
    if (opts.grid) for (const g of opts.grid) cand.push(g);
    if (!cand.length) cand.push({}, { n: 8 }, { n: 25 }, { n: 17 });
    // ייחודיות לפי canon
    const seen = new Set(); const uniq = [];
    for (const c of cand) { const key = xiCanon(c); if (seen.has(key)) continue; seen.add(key); uniq.push(c); }
    // בחירה משוקללת: מעדיף קלטים שנצפו הרבה (proxy ל-bandit)
    const scored = uniq.map((inputs, i) => ({ id: i, score: 1 + usage.filter((u) => xiCanon(u.inputs || {}) === xiCanon(inputs)).length, inputs }));
    const picks = pickWeighted(scored, Math.min(k, scored.length)).map((i) => scored[i].inputs);
    const pulse = this.mind?.last?.() || {};
    if (pulse.action === 'throttle') picks.length = Math.min(2, picks.length);
    return this.warmSite(name, picks);
  }

  async warmSite(name, inputList = []) {
    const slug = slugify(name) || 'root';
    const pol = await this._fetchPublicPolicy(slug) || this._sitePolicies?.get(slug);
    if (!pol?.dag) throw new Error('∇ warmSite: אין מדיניות לאתר "' + slug + '"');
    let n = 0;
    for (const inputs of inputList) {
      await this.runDag(pol.dag, { inputs, cache: true }).catch(() => {});
      n++;
    }
    log(`∇ חימום-כיסוי: "${slug}" — ${n} סטי-קלטים חוממו במטמון-הרשת.`);
    return { name: slug, warmed: n };
  }

  exposeSite(name, opts = {}) {
    const slug = slugify(name) || 'root';
    const path = opts.path || (`/api/site/${slug}`);
    this.route('GET', path, async (req) => {
      let inputs = opts.inputs || {};
      try {
        if (req?.query?.inputs) inputs = typeof req.query.inputs === 'string' ? JSON.parse(req.query.inputs) : req.query.inputs;
        else if (req?.query && Object.keys(req.query).length) inputs = { ...inputs, ...req.query };
      } catch {}
      const out = await this.resolveSite(name, { inputs, waitMs: opts.waitMs, live: !!opts.live, verify: !!opts.verify, cooperate: !!opts.cooperate });
      if (out.output === undefined) return { status: 404, body: { error: out.reason || 'not-found', name: out.name } };
      return { status: 200, body: { name: out.name, dagId: out.dagId, output: out.output, source: out.source, verified: out.verified } };
    }, { static: !!opts.static });
    log(`אתר-חי מחושב חשוף ב-API: GET ${path}${opts.static ? ' (+תמונה סטטית לאפס-מפעיל)' : ''}`);
    return path;
  }

  /** Door 11: קומפילציה+פרסום מ-DSL */
  async publishDSL(dsl) {
    const { name, dag } = compilePolicyDSL(dsl);
    return this.publishSite(name, dag);
  }

  /** Door 12/11: הגשת הצעה לשוק חישוב + בחירת מיטבי + עדכון best:<job> */
  async marketOffer(jobProgram, opts = {}) {
    const solved = xiSolve(jobProgram);
    const jobId = xiJobId(jobProgram);
    const bid = { solver: this._xiKeys.pubkey, digest: solved.digest, result: solved.result, price: opts.price || 0, priority: opts.priority || 0, ts: Date.now(), jobId };
    await this._publishJobResult(jobId, solved);
    if (opts.pointAs) await this.publishName(opts.pointAs, 'job:' + jobId);
    (this._marketBids ??= new Map()).set(jobId, [...(this._marketBids.get(jobId) || []), bid]);
    if (opts.autoBest !== false) await this.marketPickBest(jobId, opts).catch(() => {});
    return bid;
  }
  marketSelect(bids, opts) { return marketPick(bids, opts); }
  /** Door 11: אחרי marketPick — מצביע Roam best:<job> מתעדכן למיטבי */
  async marketPickBest(jobIdOrBids, opts = {}) {
    const jobId = typeof jobIdOrBids === 'string' ? jobIdOrBids : (jobIdOrBids?.[0]?.jobId || 'job');
    const bids = typeof jobIdOrBids === 'string' ? (this._marketBids?.get(jobId) || []) : jobIdOrBids;
    const pick = marketPick(bids, opts);
    if (pick.ok) {
      await this.publishName('best:' + jobId.slice(0, 24), 'solver:' + (pick.solver || '').slice(0, 16) + ':d:' + pick.digest);
    }
    return pick;
  }

  /** Door 13: פרסום/קיפול מכונת-מצבים משוכפלת */
  async publishMachine(name, spec) {
    const slug = slugify(name) || 'machine';
    const dag = {
      nodes: {
        events: { value: [] },
        folded: { op: 'formula', args: { expr: '0' } }, // placeholder — הקיפול נעשה ב-foldMachine
      },
      output: 'events',
    };
    // שומרים את ה-spec עצמו כמדיניות ציבורית מורחבת
    const policy = { v: 1, kind: 'state-machine', spec, dagId: hex(sha256(Buffer.from('sm:' + xiCanon(spec)))).slice(0, 40), dag };
    await this._publishPublicPolicy(slug, policy);
    await this.publishName(name, 'sm:' + policy.dagId);
    (this._machines ??= new Map()).set(slug, spec);
    return { name: slug, dagId: policy.dagId };
  }
  foldMachine(nameOrSpec, events) {
    const spec = typeof nameOrSpec === 'string' ? (this._machines?.get(slugify(nameOrSpec)) || null) : nameOrSpec;
    if (!spec) throw new Error('foldMachine: מפרט חסר');
    const folded = stateFold(spec, events);
    return { ...folded, digest: stateDigest(folded) };
  }

  /** Door 10: אירוע משתמש (login/save/…) → on: במכונה + digest משותף לכל הלקוחות */
  async siteEvent(name, event, opts = {}) {
    const slug = slugify(name) || 'machine';
    const key = 'sm:' + slug;
    const prev = (this._siteEventLog ??= new Map()).get(key) || [];
    const next = [...prev, typeof event === 'string' ? event : (event?.on || event?.type || event)];
    this._siteEventLog.set(key, next);
    const folded = this.foldMachine(slug, next);
    await this.webSet('sm:' + slug, { state: folded.state, digest: folded.digest, n: next.length, ts: Date.now() }).catch(() => {});
    if (opts.resolveSite) {
      const out = await this.resolveSite(opts.resolveSite, { inputs: { ...(opts.inputs || {}), __state: folded.state, __smDigest: folded.digest }, live: !!opts.live });
      return { folded, site: out };
    }
    return { folded };
  }

  /** Door 16: חתימת-קבלה ניידת — גוף דטרמיניסטי + אירוע Schnorr מלא לאימות אופליין */
  async signReceipt(body) {
    await this.ready();
    const core = { v: body.v, dagId: body.dagId, inputsCanon: body.inputsCanon, outputDigest: body.outputDigest, ts: body.ts, visitor: body.visitor, id: body.id };
    const payload = xiCanon(core);
    const ev = signEvent(KIND_DATA, [['d', 'screceipt:' + String(body.id || 'x').slice(0, 24)], ['t', 'receipt'], ['z', metaPad()]], payload);
    return { ...core, event: { id: ev.id, pubkey: ev.pubkey, sig: ev.sig, kind: ev.kind, created_at: ev.created_at, tags: ev.tags, content: ev.content } };
  }
  verifyReceipt(signed, output) {
    if (!signed?.event) return { ok: false, reason: 'unsigned' };
    if (!verifyReceiptBody(signed, output)) return { ok: false, reason: 'body' };
    return { ok: verifyEvent(signed.event) === true, bodyOk: true };
  }

  /** Door 14: פיצול DAG לשברים + הרכבת digests (אופרטורים טהורים בלבד) */
  async evalFragments(dag, inputs = {}, parts = 2) {
    const full = await xiClientEval(dag, inputs);
    const frags = xiFragment(dag, parts);
    const digests = [];
    for (const f of frags) {
      const nodes = { ...f.nodes };
      for (const [id, n] of Object.entries(nodes)) {
        if (n?.value && typeof n.value === 'object' && n.value.$boundary) {
          const b = n.value.$boundary;
          nodes[id] = { value: full.results?.[b] ?? null };
        }
      }
      const sub = await xiClientEval({ nodes, output: f.output }, inputs);
      digests.push(sub.dagDigest);
    }
    return { digests, assemble: xiAssembleDigests(digests), fullDigest: full.dagDigest, parts: frags.length, output: full.output };
  }

  /** Door 18: סנכרון כוונה — diff של DAG + פרסום diff (לא התוצאה) */
  intentDiff(a, b) { return dagDiff(a, b); }
  intentApply(base, diff, newerNodes) { return dagApplyDiff(base, diff, newerNodes); }
  async publishIntent(name, newerDag, opts = {}) {
    const slug = slugify(name) || 'intent';
    const prev = this._sitePolicies?.get(slug)?.dag || opts.base || { nodes: {}, output: null };
    const diff = dagDiff(prev, newerDag);
    const newerNodes = {};
    for (const id of [...diff.added, ...diff.changed]) newerNodes[id] = newerDag.nodes[id];
    if (diff.outChanged) newerNodes.__output = newerDag.output;
    await this.publishSite(slug, newerDag);
    await this.webSet('intent:' + slug, { diff, newerNodes, dagId: xiDagId(newerDag), ts: Date.now() }).catch(() => {});
    return { name: slug, diff, dagId: xiDagId(newerDag) };
  }

  /** Door 15: מיפוי תרחיש→מדיניות/UI */
  setScenarioApp(map) { this._scenarioAppMap = map || {}; return this; }

  /** Door 7: הרצת meta-router — __route בוחר מדיניות */
  async resolveMeta(routeKey, routeMap, opts = {}) {
    return this.resolveSiteDag({ nodes: { x: { value: 0 } }, output: 'x' }, { ...opts, inputs: { ...(opts.inputs || {}), __route: routeKey }, routeMap });
  }

  /** Door 9: חותם מטען ב-Σ2 AAD של liveContext */
  sealLive(plain, context) { return contextSealPayload(plain, liveContext(context || {})); }
  openLive(ct, context) { return contextOpenPayload(ct, liveContext(context || {})); }

  /** Door 12: מצב Policy-CDN — מעטפת בלבד; התוכן = resolveLive אצל המבקר */
  enablePolicyCdn(site = 'pricing') {
    this._defaultLiveSite = site;
    this._policyCdn = true;
    return this;
  }

  // ═══════════ SkyLattice-ΔB: SkyDB על Ω + checkpoints לרשת ═══════════

  /** S8: מפרסם tip digests לרשת + webkv */
  async publishDbCheckpoint() {
    if (!this._skydb) return null;
    await this.ready();
    const { body, pending } = this._skydb.checkpoint();
    const d = `skydb:chk:${keys.ns}:tip`;
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['p', keys.pubkey], ['t', 'skydb-chk'], ['z', metaPad()]], JSON.stringify(body)), 10_000, 1).catch(() => {});
    // מקטע-WAL כבד → Blossom (אם יש pending)
    if (pending.length) {
      try {
        const seg = Buffer.from(JSON.stringify(pending), 'utf8');
        if (seg.length > 256) {
          const { hash } = await blossomUpload(seg, 'application/json');
          body.walBlob = hash;
          await this.pool.publish(signEvent(KIND_DATA, [['d', `skydb:wal:${keys.ns}:${body.digest.slice(0, 12)}`], ['t', 'skydb-wal'], ['x', hash], ['z', metaPad()]], JSON.stringify({ digest: body.digest, n: pending.length, hash })), 8_000, 1).catch(() => {});
        }
      } catch {}
    }
    await this.publishName('db:tip', 'chk:' + body.digest).catch(() => {});
    await this.webSet('skydb:tip', body).catch(() => {});
    return body;
  }

  /** S13: נעיצת tip אחרי logic_fork */
  async pinDbOnFork() {
    const tips = [...(this._skydb?._tips?.entries() || [])].map(([shard, t]) => ({ shard, ...t }));
    const hist = tips.flatMap((t) => (this._skydb._history.get(t.shard) || []).map((h) => ({ ...h, shard: t.shard })));
    const maxSeq = Math.max(0, ...hist.map((h) => h.seq | 0));
    const pin = skydbPinOnFork(hist, maxSeq + 1) || this._skydb._tip(tips[0]?.shard || 's0');
    this._skydb.pinCheckpoint(pin);
    await this.publishName('db:pin', 'seq:' + (pin.seq | 0) + ':d:' + (pin.digest || '')).catch(() => {});
    return pin;
  }

  /** Ω: חושף /api/db/* — Node מלא על הכתובת כשיש עובד */
  enableDbApi(opts = {}) {
    if (this._dbApiEnabled) return this;
    this._dbApiEnabled = true;
    const db = this._skydb;
    const json = (body, status = 200) => ({ status, headers: { 'content-type': 'application/json' }, body });

    this.route('GET', '/api/health', async () => json({
      node: true, omega: true, db: db.health(), fullNode: !!(this.bridgeUrl || this._fullNodeServer), ts: Date.now(),
    }));

    this.route('GET', '/api/db/_meta/health', async () => json(db.health()), { static: !!opts.staticMeta });
    this.route('GET', '/api/db/_meta/shards', async () => {
      const peers = this._skydb.peers;
      const map = {};
      for (let i = 0; i < db.nShards; i++) {
        const key = '__probe_' + i;
        map['s' + i] = { ...skydbShardOf(key, peers, db.nShards), tip: db._tip('s' + i) };
      }
      return json({ nShards: db.nShards, peers, map });
    });

    this.route('GET', /^\/api\/db\/(?<_ns>[^/]+)\/(?<_key>.+)$/, async (req) => {
      const ns = req.params._ns, key = decodeURIComponent(req.params._key);
      if (ns.startsWith('_')) return json({ error: 'reserved' }, 400);
      if (req.headers?.['x-skydb-sealed'] === '1') {
        try { return json({ ns, key, value: db.sealedGet(ns, key, req.query || {}), sealed: true }); }
        catch (err) { return json({ error: err.message }, 403); }
      }
      const value = db.get(ns, key);
      if (value === undefined) return json({ error: 'not-found', ns, key }, 404);
      return json({ ns, key, value });
    });

    this.route('PUT', /^\/api\/db\/(?<_ns>[^/]+)\/(?<_key>.+)$/, async (req) => {
      const ns = req.params._ns, key = decodeURIComponent(req.params._key);
      let body = {};
      try { body = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || req.body || '{}'); } catch {}
      const value = body.value !== undefined ? body.value : body;
      try {
        const out = body.sealed
          ? await db.sealedPut(ns, key, value, body.ctx || {}, { receipt: body.receipt, policy: body.policy, policyInputs: body.policyInputs })
          : await db.put(ns, key, value, { receipt: body.receipt, policy: body.policy, policyInputs: body.policyInputs, requireReceipt: !!body.requireReceipt });
        return json(out);
      } catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('POST', /^\/api\/db\/(?<_ns>[^/]+)\/(?<_key>.+)$/, async (req) => {
      const ns = req.params._ns, key = decodeURIComponent(req.params._key);
      let body = {};
      try { body = typeof req.json === 'function' ? req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      const value = body.value !== undefined ? body.value : body;
      try {
        const out = body.sealed
          ? await db.sealedPut(ns, key, value, body.ctx || {}, { receipt: body.receipt, policy: body.policy })
          : await db.put(ns, key, value, { receipt: body.receipt, policy: body.policy, requireReceipt: !!body.requireReceipt });
        return json(out);
      } catch (err) { return json({ error: err.message }, 403); }
    });

    this.route('POST', '/api/db/batch', async (req) => {
      let body = {};
      try { body = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try { return json({ results: await db.batch(body.ops || [], body) }); }
      catch (err) { return json({ error: err.message }, 403); }
    });

    this.route('POST', '/api/db/_meta/checkpoint', async () => json(await this.publishDbCheckpoint()));
    log('ΔB/SkyDB: Ω API פעיל — /api/health · /api/db/* (נתיב-חם מקומי; checkpoints לרשת).');
    return this;
  }

  /** מפעיל שרת Node מלא ב-site/server.js + bridge */
  async startFullNode(opts = {}) {
    const port = opts.port || FULL_NODE_PORT;
    this.enableDbApi(opts);
    const serverPath = path.join(ROOT, 'site', 'server.js');
    if (!fs.existsSync(serverPath)) throw new Error('חסר site/server.js — צור את שרת ה-Node המלא');
    if (!this._fullNodeServer) {
      // מריצים את השרת באותו תהליך דרך require (לא child) — חולקים את cloud.db
      const create = require(serverPath);
      this._fullNodeServer = await create({ cloud: this, port });
      log(`Full-Node: site/server מאזין על http://127.0.0.1:${port}`);
    }
    this.bridge(`http://127.0.0.1:${port}`);
    return this;
  }

  // ════════════ SkyLattice-Φ: זרימה בזמן-אמת המחוברת למנוע ("תדרים") ════════════
  clock(hz) { const c = new Clock(); if (hz) c.start(hz); (this._clocks ??= []).push(c); return c; }
  streamFromWeb(key) {
    const s = new Stream();
    this.onWebChange?.((k, v) => { if (k === key) s.push(v, Date.now()); });
    return s;
  }
  flowToDag(stream, buildDag, onResult) {
    return stream.subscribe(async (tuple, epoch) => {
      try { const out = await this.runDag(buildDag(tuple, epoch)); onResult?.(out, epoch); }
      catch (err) { log('Φ→Ξ: הרצת-צנרת נכשלה:', err.message); }
    });
  }
  /** Door 3: Φ→∇ — כל פעימת-זרם מזינה resolveSite (דשבורד חי אצל כל מבקר/peer) */
  flowToSite(stream, siteName, mapInputs, onResult) {
    return stream.subscribe(async (tuple, epoch) => {
      try {
        const inputs = typeof mapInputs === 'function' ? mapInputs(tuple, epoch) : { ...(mapInputs || {}), value: tuple, epoch };
        const out = await this.resolveSite(siteName, { inputs, live: true });
        onResult?.(out, epoch);
      } catch (err) { log('Φ→∇: resolveSite נכשל:', err.message); }
    });
  }

  // ═══════════ SkyLattice-Roam: שם קריא, משתנה ונודד לתוכן/אתר/צנרת ═══════════
  _dName(slug) { return `scnm:${keys.ns}:${slug}`; }

  _dNameHist(slug, seq) { return `scnmh:${keys.ns}:${slug}:${seq | 0}`; }

  async _roamCollect(slug, waitMs = 3000) {
    await this.ready();
    const res = new RoamResolver();
    return new Promise((resolve) => {
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [keys.pubkey], '#d': [this._dName(slug)], limit: 32 }],
        (ev) => { try { if (verifyEvent(ev) !== true) return; res.ingest(JSON.parse(decrypt(ev.content, this._dName(slug)).toString())); } catch {} },
        () => {},
      );
      // Door 8: גם היסטוריית-גרסאות (ל-time-travel)
      this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [keys.pubkey], '#t': ['roam-hist'], '#n': [slug], limit: 64 }],
        (ev) => { try { if (verifyEvent(ev) !== true) return; const d = (ev.tags.find((t) => t[0] === 'd') || [])[1]; res.ingest(JSON.parse(decrypt(ev.content, d).toString())); } catch {} },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve(res); }, waitMs);
    });
  }

  /** מפרסם/מעדכן שם קריא → יעד. seq עולה; כל גרסה נשמרת גם ב-hist ל-time-travel */
  async publishName(name, target, opts = {}) {
    await this.ready(); this.startLive();
    const slug = slugify(name) || 'root';
    const res = await this._roamCollect(slug, opts.waitMs || 2500);
    const prev = res.current;
    const rec = roamRecord(slug, target, (prev ? prev.seq + 1 : 0), prev ? prev.hash : null, now());
    const d = this._dName(slug);
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['t', 'roam'], ['z', metaPad()]], encrypt(Buffer.from(JSON.stringify(rec)), d)), 12_000, 2);
    const dh = this._dNameHist(slug, rec.seq);
    await this.pool.publish(signEvent(KIND_DATA, [['d', dh], ['t', 'roam-hist'], ['n', slug], ['z', metaPad()]], encrypt(Buffer.from(JSON.stringify(rec)), dh)), 10_000, 1).catch(() => {});
    (this._roamNames ??= new Map()).set(slug, rec);
    log(`Roam: השם "${slug}" עודכן → ${JSON.stringify(target)} (seq=${rec.seq}).`);
    return { name: slug, seq: rec.seq, hash: roamHash(rec), target };
  }

  /** פותר שם → יעד; opts.seq = time-travel לגרסה היסטורית */
  async resolveName(name, opts = {}) {
    const slug = slugify(name) || 'root';
    const res = await this._roamCollect(slug, opts.waitMs || 3500);
    if (opts.seq != null && opts.seq !== '') {
      const target = res.atSeq(opts.seq);
      return { name: slug, target, seq: opts.seq | 0, gaps: res.gaps, historical: true };
    }
    return { name: slug, target: res.resolve(), seq: res.current?.seq ?? null, gaps: res.gaps };
  }

  /** נדידה: מרעננים את רשומות-השם + בוחרים תת-קבוצת ממסרים מסתובבת לשכפול */
  async _roamSweep() {
    if (!this._roamNames || !this._roamNames.size) return;
    const rep = this.pool.healthReport().map((r) => ({ id: r.url ?? r.relay, score: r.score ?? 0 }));
    const subset = roamRelaySubset(rep, Math.max(2, Math.ceil(rep.length / 2)), (this._roamEpoch = (this._roamEpoch || 0) + 1));
    for (const [slug, rec] of this._roamNames) {
      const d = this._dName(slug);
      try { await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['t', 'roam'], ['z', metaPad()]], encrypt(Buffer.from(JSON.stringify(rec)), d)), 10_000, 1); } catch {}
    }
    if (subset.length) log(`Roam: נדידת-שכפול — ${this._roamNames.size} שמות רועננו; מיקום נוכחי סובב אל ${subset.length} ממסרים בריאים.`);
  }

  // ─────────── תרומת-עיבוד / כרייה — הסכמה מפורשת בלבד, לעולם לא בשקט ───────────
  // עקרון: הרצת עומס-מעבד אצל מי שלא אישר במפורש = "cryptojacking" (זדוני,
  // חסום ע"י דפדפנים/אנטי-וירוס ואף לא חוקי במקומות רבים). לכן המנגנון כאן
  // כבוי כברירת מחדל, דורש גם MINING.enabled=true וגם דגל --allow-mining
  // בזמן ריצה, מווסת עומס לפי maxCpuPercent, ושקוף לחלוטין (מדפיס מה שהוא עושה).
  startMining(consented) {
    if (!MINING.enabled) return;
    if (!consented) { log('כרייה: MINING.enabled=true אך לא ניתן אישור ריצה (--allow-mining). לא מופעל.'); return; }
    if (!MINING.wallet || !MINING.network) { log('כרייה: חובה להגדיר MINING.wallet ו-MINING.network. לא מופעל.'); return; }
    const duty = Math.max(1, Math.min(90, MINING.maxCpuPercent)) / 100; // מחזור-עבודה
    const sliceMs = 50, workMs = Math.round(sliceMs * duty), restMs = sliceMs - workMs;
    let hashes = 0, nonce = 0;
    const seed = Buffer.from(`skymine:${MINING.coin}:${MINING.wallet}:${keys.pubkey}`);
    log(`כרייה/תרומת-עיבוד פעילה (באישור מפורש): מטבע=${MINING.coin || '?'} רשת=${MINING.network} תקרה=${MINING.maxCpuPercent}% ארנק=${MINING.wallet.slice(0, 10)}…`);
    log('שקיפות: זהו מנוע-עבודה מווסת. לחיבור לפּוּל אמיתי ממש את onShare/network בהתאם לפרוטוקול המטבע.');
    const tick = () => {
      const end = Date.now() + workMs;
      while (Date.now() < end) { sha256(seed, b32(BigInt(nonce++))); hashes++; }
    };
    this.miningTimer = setInterval(tick, sliceMs + restMs);
    this.miningTimer.unref?.();
    this.miningStat = setInterval(() => log(`כרייה: ${(hashes / 10).toFixed(0)} hash/s (מווסת ל-${MINING.maxCpuPercent}%).`), 10_000);
    this.miningStat.unref?.();
  }

  // ─────────────── מצב חי: מנוי + צפייה בקבצים ───────────────

  _suppress(rel) {
    this._suppressed ??= new Map();
    this._suppressed.set(rel, Date.now() + 3000);
  }

  startLive() {
    if (this.liveStarted) return;
    this.liveStarted = true;
    // מנוי לעדכונים חיים ממקומות אחרים עם אותו ID
    this.pool.subscribe(
      [{ kinds: [KIND_DATA], authors: [keys.pubkey], '#d': [dManifest(), dKv()], since: now() - 5 }],
      (ev) => {
        const d = ev.tags.find((t) => t[0] === 'd')?.[1];
        try {
          const payload = JSON.parse(decrypt(ev.content, d).toString('utf8'));
          if (d === dKv()) { this._mergeKv(payload); return; }
          if (d === dManifest() && payload.device !== DEVICE && payload.updatedAt > this.manifest.updatedAt) {
            if (this._isRollback(payload)) return;
            this._acceptManifest(payload);
            log('עדכון חי התקבל מהרשת — מסנכרן…');
            this._queueSync();
          }
        } catch {}
      },
    );

    // מנוי חי לנתוני האתר (webkv) — מכל מבקר, מכל מקום בעולם
    this.pool.subscribe(
      [{ kinds: [KIND_DATA], '#p': [keys.pubkey], since: now() - 5 }],
      (ev) => this._mergeWebEvent(ev),
    );

    // צפייה בשינויים מקומיים
    try {
      this.watcher = fs.watch(ROOT, { recursive: true }, (_evt, fname) => {
        // התעלמות משינויים שנוצרו ע"י פקודת הבנייה עצמה — מונע לולאת בנייה אינסופית
        if (isBuildQuiet()) return;
        if (!fname) return this._queueSync();
        const rel = String(fname).replace(/\\/g, '/');
        const top = rel.split('/')[0];
        if (IGNORE.includes(top) || IGNORE.includes(path.basename(rel)) || rel.startsWith('.cloud-')) return;
        const until = this._suppressed?.get(rel);
        if (until && Date.now() < until) return;
        this._queueSync();
      });
    } catch (err) {
      log('אזהרה: מעקב קבצים אוטומטי לא זמין (' + err.message + ') — יבוצע סקר תקופתי בלבד.');
    }

    // סקר תקופתי כגיבוי — גם אם המנוי החי נפל
    this.pollTimer = setInterval(() => {
      this._pullRemote().then(() => this._queueSync()).catch(() => {});
    }, LIVE_POLL_MS);

    // Θ: מחזור ריפוי-עצמי — רענון שכפול ואימות שלמות אוטומטי ברשת
    this.healTimer = setInterval(() => this._healSweep(), HEAL_SWEEP_MS);
    this.healTimer.unref?.();

    // Θ: ניתוח מסלולים והחלפה דינמית — Auto-Proxy / Protocol Hopping
    this.hopTimer = setInterval(() => this.pool.autoHop(), 30_000);
    this.hopTimer.unref?.();

    log('מצב חי פעיל — כל שינוי כאן או בכל מקום אחר עם אותו ID יסתנכרן אוטומטית.');
  }

  _queueSync() {
    clearTimeout(this.pendingScan);
    this.pendingScan = setTimeout(() => {
      this.syncing = this.syncing.then(() => this._reconcile()).catch((err) => log('שגיאת סינכרון:', err.message));
    }, DEBOUNCE_MS);
  }

  async stop() {
    clearInterval(this.pollTimer);
    clearInterval(this.healTimer);
    clearInterval(this.hopTimer);
    clearInterval(this.miningTimer);
    clearInterval(this.miningStat);
    clearTimeout(this.pendingScan);
    clearTimeout(this.siteRetryTimer);
    this.watcher?.close();
    await this.syncing.catch(() => {});
    this.pool.close();
  }
}

// ────────────────────────────── הפעלה ──────────────────────────────

const cloud = new Cloud();

async function main() {
  const args = process.argv.slice(2);

  if (args.includes('--new-id')) {
    const self = fs.readFileSync(__filename, 'utf8');
    const updated = self.replace(/const SYNC_ID = '[^']*';/, `const SYNC_ID = 'AUTO';`);
    fs.writeFileSync(__filename, updated, 'utf8');
    try { fs.unlinkSync(STATE_FILE); } catch {}
    log('ה-ID אופס. בהרצה הבאה ייווצר ענן חדש וריק.');
    return;
  }

  if (args[0] === '--shares') {
    ensureId();
    const n = Math.max(2, parseInt(args[1], 10) || 5);
    const k = Math.min(n, Math.max(2, parseInt(args[2], 10) || 3));
    const shares = shamirSplit(Buffer.from(syncId, 'utf8'), n, k);
    log(`Δ2: ה-SYNC_ID פוצל ל-${n} רסיסים; כל ${k} מהם משחזרים אותו במלואו:`);
    for (const s of shares) log('  ' + s);
    log('שמור כל רסיס במקום נפרד. רסיס בודד (או פחות מ-' + k + ') לא חושף שום דבר מה-ID.');
    log('שחזור: node cloud.js --recover <רסיס> <רסיס> <רסיס>');
    return;
  }

  if (args[0] === '--import') {
    if (!args[1]) { log('שימוש: node cloud.js --import <file.zip> [--dir <תיקיית-יעד>]'); return; }
    const di = args.indexOf('--dir');
    ensureId(); deriveKeys();
    await cloud.importZip(args[1], di > 0 ? { dir: args[di + 1] } : {});
    log('הייבוא הושלם. הרץ "node cloud.js --deploy" כדי לבנות ולפרסם לכתובת הגלובלית.');
    return;
  }

  if (args[0] === '--formula') {
    ensureId(); deriveKeys();
    const expr = args.slice(1).join(' ');
    if (!expr) { log('שימוש: node cloud.js --formula "clamp(vitality*100, 0, 100)"'); return; }
    await cloud.ready();
    try { log('Λ: תוצאת הנוסחה =', cloud.evalFormula(expr)); }
    catch (err) { log('Λ: שגיאת נוסחה —', err.message); }
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--solve') {
    ensureId(); deriveKeys();
    let program;
    try { program = JSON.parse(args.slice(1).join(' ')); } catch { log('שימוש: node cloud.js --solve \'{"op":"factorize","args":{"n":600851475143}}\''); return; }
    const qi = args.indexOf('--quorum');
    const out = await cloud.compute(program, qi > 0 ? { quorum: parseInt(args[qi + 1], 10) || 2 } : {});
    log(`Ξ: jobId=${out.jobId}\nמקור=${out.source} · הסכמה=${out.agree}\nתוצאה=${JSON.stringify(out.result)}`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--dag') {
    ensureId(); deriveKeys();
    let dag;
    try { dag = JSON.parse(args.slice(1).join(' ')); } catch { log('שימוש: node cloud.js --dag \'{"nodes":{"a":{"op":"primes","args":{"upTo":100}},"b":{"op":"formula","args":{"expr":"c*2","vars":{"c":{"$ref":"a","path":"count"}}}}},"output":"b"}\''); return; }
    const out = await cloud.runDag(dag);
    log(`Ξ/DAG: dagId=${out.dagId}\nמקור=${out.source}\nפלט=${JSON.stringify(out.output)}`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--publish-site') {
    ensureId(); deriveKeys();
    let dag;
    try { dag = JSON.parse(args.slice(2).join(' ')); } catch { log('שימוש: node cloud.js --publish-site <שם> \'{"nodes":{...},"output":"..."}\''); return; }
    const out = await cloud.publishSite(args[1], dag);
    log(`אתר-חי מחושב: "${out.name}" → dag:${out.dagId}`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--dsl') {
    ensureId(); deriveKeys();
    const text = args.slice(1).join(' ').replace(/\\n/g, '\n');
    if (!text) { log('שימוש: node cloud.js --dsl "policy x\\nin n = 25\\n..."'); return; }
    const out = await cloud.publishDSL(text);
    log(`DSL→אתר: "${out.name}" dag:${out.dagId} collapsed=${out.collapsed}`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--policy-kit') {
    // E2E: מפרסם ערכת-מדיניות לרשת + מעטפת Policy-CDN (index/skylive/SDK) לשער nsite
    cloud.forceVerifyBlobs = true;
    await cloud.ready();
    const siteArg = args[1] && !String(args[1]).startsWith('--') ? args[1] : 'pricing';
    const out = await cloud.publishPolicyKit({ site: siteArg });
    await cloud._publishWebsite().catch((err) => log('אזהרה: פרסום מעטפת האתר נכשל:', err.message));
    log(`Policy-kit: ${out.kit.join(', ')} · site=${out.site} · cdn=${!!out.cdn}`);
    log('כתובת (Policy CDN):', cloud.address());
    if (PUBLIC_WEBSITE) for (const m of mirrorAddresses().slice(1)) log('  מראה:', m);
    // אם גם --deploy — ממשיכים לזרימת הסינכרון הרגילה (כבר ready); אחרת יוצאים
    if (!args.includes('--deploy') && !args.includes('--once')) {
      await cloud.stop();
      process.exit(0);
    }
  }

  if (args[0] === '--resolve-site') {
    ensureId(); deriveKeys();
    if (!args[1]) { log('שימוש: node cloud.js --resolve-site <שם> [inputs-json]'); return; }
    let inputs; if (args[2]) { try { inputs = JSON.parse(args.slice(2).join(' ')); } catch {} }
    const out = await cloud.resolveSite(args[1], { inputs });
    log(out.output !== undefined ? `אתר-חי ∇: "${out.name}" (dag:${out.dagId}) → ${JSON.stringify(out.output)}\nמקור=${out.source} · from=${out.from}` : `אתר-חי: "${out.name}" לא נפתר (${out.reason}).`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--name') {
    ensureId(); deriveKeys();
    if (args.length < 3) { log('שימוש: node cloud.js --name <שם> <יעד: hash/URL/jobId/dagId>'); return; }
    const out = await cloud.publishName(args[1], args.slice(2).join(' '));
    log(`Roam: "${out.name}" → ${out.target} (seq=${out.seq}, hash=${out.hash})`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--resolve') {
    ensureId(); deriveKeys();
    if (!args[1]) { log('שימוש: node cloud.js --resolve <שם>'); return; }
    const out = await cloud.resolveName(args[1]);
    log(out.target !== undefined ? `Roam: "${out.name}" → ${out.target} (seq=${out.seq})` : `Roam: השם "${out.name}" לא נמצא ברשת.`);
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--recover') {
    if (args.length < 3) { log('שימוש: node cloud.js --recover <רסיס1> <רסיס2> [...]'); return; }
    const secret = shamirJoin(args.slice(1));
    log('Δ2: הסוד המשוחזר: ' + secret);
    if (!/^SC-[0-9A-F]{40}$/.test(secret)) {
      log('אזהרה: התוצאה לא נראית כמו SYNC_ID תקין — כנראה סופקו פחות רסיסים');
      log('מהסף (K) שנקבע בפיצול, או רסיס שגוי. נסה שוב עם רסיסים נוספים.');
    }
    return;
  }

  if (args.includes('--status')) {
    ensureId();
    deriveKeys();
    log('SYNC_ID:', syncId);
    log('שם הפרויקט:', PROJECT_NAME, `[${SITE_SLUG}]`);
    log('כתובת האתר החי:', globalAddress());
    if (PUBLIC_WEBSITE) for (const m of mirrorAddresses().slice(1)) log('  מראה:', m);
    if (CUSTOM_DOMAIN) printDomainSetup();
    const sd = resolveSiteDir();
    if (sd) log('תיקיית האתר:', sd + '/');
    const bat = resolveBatEntry();
    if (bat) log(`קובץ הפעלה: ${bat.bat} → דף הבית: ${bat.html}`);
    if (BUILD_COMMAND) log('פקודת בנייה:', BUILD_COMMAND);
    const st = loadState();
    log('קבצים במעקב מקומי:', Object.keys(st.files ?? {}).length);
    return;
  }

  if (args.includes('--deploy')) cloud.forceVerifyBlobs = true;
  await cloud.ready();

  if (args.includes('--once') || args.includes('--deploy')) {
    log('סינכרון ופרסום הושלמו. המידע והאתר שמורים ברשת וימשיכו להתקיים גם כשהכול כבוי.');
    await cloud.stop();
    process.exit(0);
  }

  cloud.startLive();
  if (args.includes('--serve-compute')) cloud.serveCompute(); // Ξ: פותר-עצמאי ברשת
  // Full-Node: SkyDB Ω + site/server + bridge (או LOCAL_BRIDGE ידני)
  if (args.includes('--full-node') || FULL_NODE) {
    await cloud.startFullNode({ port: FULL_NODE_PORT }).catch((err) => log('Full-Node נכשל:', err.message));
  } else if (LOCAL_BRIDGE) {
    cloud.bridge(LOCAL_BRIDGE);
  }
  if (MINING.enabled) cloud.startMining(args.includes('--allow-mining'));
  if (args.includes('--health')) setInterval(() => { for (const r of cloud.health()) log('Θ', r.relay, `lat=${r.latency}ms phi=${r.phi} score=${r.score}`); }, 15_000);
  if (args.includes('--mind')) setInterval(async () => {
    await cloud._selfAudit(); const p = cloud._pulse();
    log(`Λ דופק: חיוניות ${(p.vitality * 100).toFixed(0)}% · אנטרופיה ${p.entropy} · אודיט ${cloud.audit.ok ? 'תקין' : 'נכשל'} · החלטה: ${p.action}`);
    const top = cloud.relayBandit.ranking()[0]; if (top) log(`Λ הממסר הנלמד כמיטבי: ${top.arm} (תגמול ${top.mean.toFixed(3)})`);
  }, 15_000);
  const shutdown = async () => {
    log('סוגר… המידע כבר שמור ברשת ויהיה זמין מכל מקום.');
    await cloud.stop();
    process.exit(0);
  };
  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);
}

if (require.main === module) {
  main().catch((err) => { console.error('[cloud] שגיאה קריטית:', err.message); process.exit(1); });
} else {
  // מוטמע כמודול בתוך אפליקציה — API שקט ללא UI
  module.exports = {
    ready: () => cloud.ready().then(() => cloud.startLive()).then(() => cloud),
    // ענן פרטי (מוצפן — רק מי שמחזיק את ה-SYNC_ID)
    set: (k, v) => cloud.set(k, v),
    get: (k) => cloud.get(k),
    getAll: () => cloud.getAll(),
    onChange: (fn) => cloud.onChange(fn),
    // נתונים חיים משותפים עם האתר הציבורי (webkv)
    webSet: (k, v) => cloud.webSet(k, v),
    webGet: (k) => cloud.webGet(k),
    webGetAll: () => cloud.webGetAll(),
    onWebChange: (fn) => cloud.onWebChange(fn),
    // רשת החישוב המבוזרת (SkyLattice) + העלאת קבצים ציבוריים
    serve: (handlers) => cloud.serve(handlers),
    run: (task, payload, timeoutMs) => cloud.run(task, payload, timeoutMs),
    upload: (input, type) => cloud.upload(input, type),
    // שרת אפליקציה חי על הכתובת הגלובלית (SkyLattice-Ω)
    route: (method, path, fn, opts) => cloud.route(method, path, fn, opts),
    bridge: (url) => cloud.bridge(url),
    fetchApi: (p, init) => cloud.fetchApi(p, init),
    // תמונת-API סטטית — נענית מהרשת גם ללא אף עובד-שרת חי (ללא-מפעיל)
    snapshot: (p, value, opts) => cloud.snapshot(p, value, opts),
    // ייבוא מערכת שלמה מ-ZIP + ניטור בריאות עצמי (Θ)
    importZip: (zip, opts) => cloud.importZip(zip, opts),
    health: () => cloud.health(),
    // SkyLattice-Λ: שכבת-הקוגניציה — דופק/מודל-עצמי, אודיט, נוסחאות בזמן אמת
    cognition: () => cloud.cognition(),
    pulse: () => cloud._pulse(),
    selfAudit: () => cloud._selfAudit(),
    evalFormula: (f, vars) => cloud.evalFormula(f, vars),
    // SkyLattice-Ξ: רשת-החישוב הדטרמיניסטית — חישוב טהור חופשי-מהרשת
    compute: (program, opts) => cloud.compute(program, opts),
    serveCompute: (opts) => cloud.serveCompute(opts),
    jobId: (program) => xiJobId(program),
    // Ξ/DAG: צנרת-חישוב שמצטברת ברשת
    runDag: (dag, opts) => cloud.runDag(dag, opts),
    dagId: (dag) => xiDagId(dag),
    // Φ: זרימה בזמן-אמת ("תדרים")
    clock: (hz) => cloud.clock(hz),
    streamFromWeb: (key) => cloud.streamFromWeb(key),
    flowToDag: (stream, buildDag, onResult) => cloud.flowToDag(stream, buildDag, onResult),
    Stream, Clock, Channel, FormulaVM,
    // Roam: שם קריא, משתנה ונודד
    publishName: (name, target, opts) => cloud.publishName(name, target, opts),
    resolveName: (name, opts) => cloud.resolveName(name, opts),
    // אתר-חי מחושב: שם → צנרת-DAG מותנית → תוכן דינמי אפס-מפעיל
    publishSite: (name, dag) => cloud.publishSite(name, dag),
    resolveSite: (name, opts) => cloud.resolveSite(name, opts),
    resolveLive: (name, ctx, opts) => cloud.resolveLive(name, ctx, opts),
    exposeSite: (name, opts) => cloud.exposeSite(name, opts),
    warmSite: (name, inputs) => cloud.warmSite(name, inputs),
    smartWarm: (name, opts) => cloud.smartWarm(name, opts),
    clientEval: (dag, inputs) => xiClientEval(dag, inputs),
    publishPolicy: (name, dag) => cloud.publishPolicy(name, dag),
    publishPolicyKit: (opts) => cloud.publishPolicyKit(opts),
    publishDSL: (dsl) => cloud.publishDSL(dsl),
    getPolicy: (name, opts) => cloud.getPolicy(name, opts),
    listPolicies: () => cloud.listPolicies(),
    composePolicies: (map, opts) => cloud.composePolicies(map, opts),
    flowToSite: (stream, name, map, onResult) => cloud.flowToSite(stream, name, map, onResult),
    marketOffer: (prog, opts) => cloud.marketOffer(prog, opts),
    marketSelect: (bids, opts) => cloud.marketSelect(bids, opts),
    marketPickBest: (job, opts) => cloud.marketPickBest(job, opts),
    publishMachine: (name, spec) => cloud.publishMachine(name, spec),
    foldMachine: (name, events) => cloud.foldMachine(name, events),
    siteEvent: (name, ev, opts) => cloud.siteEvent(name, ev, opts),
    signReceipt: (body) => cloud.signReceipt(body),
    verifyReceipt: (signed, out) => cloud.verifyReceipt(signed, out),
    evalFragments: (dag, inputs, parts) => cloud.evalFragments(dag, inputs, parts),
    intentDiff: (a, b) => cloud.intentDiff(a, b),
    intentApply: (base, diff, nodes) => cloud.intentApply(base, diff, nodes),
    publishIntent: (name, dag, opts) => cloud.publishIntent(name, dag, opts),
    resolveMeta: (key, map, opts) => cloud.resolveMeta(key, map, opts),
    sealLive: (plain, ctx) => cloud.sealLive(plain, ctx),
    openLive: (ct, ctx) => cloud.openLive(ct, ctx),
    enablePolicyCdn: (site) => cloud.enablePolicyCdn(site),
    setScenarioApp: (map) => cloud.setScenarioApp(map),
    creditWarmer: (who, site) => cloud.creditWarmer(who, site),
    reportBranch: (site, trace) => cloud.reportBranch(site, trace),
    scenarios: () => cloud.scenarios.last,
    compilePolicyDSL: (text) => compilePolicyDSL(text),
    POLICY_KIT,
    // SkyLattice-ΔB / SkyDB
    get db() { return cloud.db; },
    enableDbApi: (opts) => cloud.enableDbApi(opts),
    startFullNode: (opts) => cloud.startFullNode(opts),
    publishDbCheckpoint: () => cloud.publishDbCheckpoint(),
    pinDbOnFork: () => cloud.pinDbOnFork(),
    SkyDB, skydbShardOf, skydbDigest,
    // הודעות מוצפנות מקצה-לקצה (Ψ) — התכתבות עם משתמשי האתר
    sendTo: (pub, value) => cloud.sendTo(pub, value),
    onMessage: (fn) => cloud.onMessage(fn),
    messages: () => cloud.messages(),
    address: () => { ensureId(); deriveKeys(); return cloud.address(); },
    stop: () => cloud.stop(),
    get id() { ensureId(); return syncId; },
    // הזהות הציבורית של הפרויקט — אליה משתמשי האתר שולחים הודעות (SkyCloud.owner)
    get pubkey() { ensureId(); if (!keys) deriveKeys(); return keys.pubkey; },
  };
}

// חשיפת פונקציות פנימיות לבדיקות בלבד (רץ רק כשמוגדר משתנה סביבה SC_TEST)
if (process.env.SC_TEST && module.exports) { module.exports.__test = { encrypt, decrypt, signEvent, verifyEvent, schnorrVerify, ensureId, deriveKeys, sha256, rsEncode, rsDecode, shamirSplit, shamirJoin, ecdhKey, psiSeal, psiOpen, ptMul, G, N, b32, big, hex, unhex, unzip, hlcNow, hlcObserve, RelayHealth, RelayPool, zlib, buildWebSdk, pickWeighted, shuffle, runFingerprint, metaPad, RUN_SALT, FormulaVM, AdaptiveLayer, Bandit, LayerStack, SelfModel, fedAverage, mulberry32, seedFromStr, sigmoid, XI_OPS, xiCanon, xiJobId, xiSolve, xiDigest, xiQuorum, XI_LIMITS, xiRefs, xiPick, xiSubst, xiDagPlan, xiDagId, xiDagEval, xiNodeRefs, xiHasSkip, XI_SKIP, xiCollapse, xiCollapseFull, xiCollapseFormula, xiClientEval, xiExpandNested, xiPolicyRefs, compilePolicyDSL, stateFold, stateDigest, marketPick, ScenarioDetector, liveContext, Clock, Stream, Channel, roamRecord, roamHash, RoamResolver, roamRelaySubset, POLICY_KIT, metaRoute, contextBind, contextCheck, contextSealPayload, contextOpenPayload, makeReceiptBody, verifyReceiptBody, branchTrace, consensusBranches, xiFragment, xiAssembleDigests, dagDiff, dagApplyDiff, scenarioAppSelect, SkyDB, skydbDigest, skydbShardOf, skydbSchemaDiff, skydbPinOnFork }; }
