#!/usr/bin/env node
'use strict';
/*
 * ═══════════════════════════════════════════════════════════════════════════
 *  SKYCLOUD — ענן פרטי וסינכרון, קובץ אחד, ללא שרת, ללא חשבון, ללא UI
 * ═══════════════════════════════════════════════════════════════════════════
 *
 *  איך משתמשים:
 *    1. זורקים את הקובץ הזה (cloud.js) לתיקייה של כל פרויקט.
 *    2. מריצים:  node cloud.js
 *       בהרצה הראשונה נוצר SYNC_ID ייחודי ונכתב אוטומטית לתוך הקובץ (למטה).
 *    3. במחשב/מקום אחר: שמים את אותו cloud.js עם אותו SYNC_ID ומריצים.
 *       הכול מסתנכרן — גם אם המקור הראשון כבוי לגמרי.
 *
 *  מדריך שלבים מלא (העלאה / הורדה / בנייה / פרויקט מלא / סוכני AI):
 *      AI-AGENT-GUIDE.md §2.4  ·  README.md «מדריך שלבים»
 *      חיבור עם ID ממקור אחר: §2.5 (העתקת קובץ / הדבקת ID / --recover --write)
 *      נושאים שסוכנים מפספסים: §2.6
 *      אפליקציה מלאה בכתובת + סנכרון משתמשים + EXE/APK: §2.7
 *      קובץ אחד = שים בתיקייה + --once | פרויקט שלם = SITE_DIR + --deploy
 *      הורדה = אותו cloud.js (אותו SYNC_ID) + --once | בלי --new-id
 *      FULL_APP_STRICT=true → לא מפרסמים tip חלקי
 *      --status מציג תוכנית פרסום + לקוח מקורי (inferNativeClientPlan)
 *      node cloud.js --embed-client   הטמעת bridge/מניפסט לפרויקט (APK/EXE/WebView)
 *      node cloud.js --import-local  ייבוא data/local מקומי לענן
 *      node cloud.js --client-update פרסום ערוץ עדכון למתקינים (נפרד מכתובת)
 *      node cloud.js --seal-engine / --publish-engine / --pull-engine
 *        ערוץ אחים: אטימה+חתימה מהמקור → עדכון גוף בכל העותקים (שומר SYNC_ID)
 *      AI-AGENT-GUIDE §2.7–§2.9 (חיבור קובץ/חלק/מידע/ZIP/פרויקט שלם)
 *      node cloud.js --connect-plan [file|folder|data|zip|whole]
 *
 *  הטמעה בתוך קוד (ללא 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 --recovery-kit     ייצוא ערכת שחזור (בלי SYNC_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 מלא)
 *      node cloud.js --pack [dir]        אריזת אפליקציה לקובץ JS יחיד (S16 Capsule)
 *      node cloud.js --handle [slug]     פרסום SkyHandle קצר מעל ה-npub (Roam)
 *      node cloud.js --address-stack     JSON: כתובת קצרה (סיומת המנוע) + קנונית + מראות
 *      node cloud.js --mind-api          SkyMind: LLM חיצוני דרך המנוע (∇→delta→cache→API)
 *      node cloud.js --physics-lattice   Mesh+CDN+Address+SLA (עקיפת מגבלות מוצר)
 *      node cloud.js --living-address    כתובת גלובלית = Node מלא (mesh+∇+snapshots; כמו דומיין)
 *      node cloud.js --side-effect-node  Living + bridge/bridge + dial ייעודי ל-side-effects
 *      node cloud.js --space-mesh        NASA-grade DTN/CGR/orbital-dance multi-path control plane
 *      node cloud.js --immortal          שכפול עצמי על אנטנות הרשת — חי גם בלי מקור נגיש
 *      node cloud.js --capsule-mesh [dir]  S37: אריזה+פריסת קפסולה לרשת עובדים
 *      node cloud.js --capsule-mesh [dir] --canary [--promote]  S44 canary mesh
 *      node cloud.js --visitor-seed       S45: stub ∇ לדפדפן
 *
 *  שרת אפליקציה חי על הכתובת הגלובלית (Ω) — לא סטטי:
 *      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' = ייווצר לבד בהרצה הראשונה וייכתב לכאן אוטומטית.
// כדי לחבר מקום/פרויקט אחר לאותו ענן — אחת מהדרכים (פירוט: AI-AGENT-GUIDE §2.5):
//   (א) להעתיק את cloud.js הזה עם ה-ID שבפנים;
//   (ב) להדביק כאן SYNC_ID ממקור אחר (לא להשאיר AUTO);
//   (ג) node cloud.js --recover <רסיסים> --write
// פרויקט חדש: השאירו 'AUTO' (נכתב אוטומטית בהרצה ראשונה). אל תעלו SYNC_ID אמיתי ל-repo ציבורי.
const SYNC_ID = 'SC-372514B0C4747E61ED1115A8D5A9695C14962C59';

// ─── Engine Siblings (ערוץ אחים) ───
// ENGINE_SOURCE=true + ENGINE_SOURCE_ROOT המדויק = רק מכאן מפרסמים גוף מוצפן לכל האחים.
// עותקים מבוזרים: loader + cloud.engine.seal (AES-GCM) — עריכת knobs בלבד, לא קוד.
const ENGINE_SOURCE = true;
const ENGINE_VERSION = '1.2.1';
const ENGINE_CHANNEL = 'skycloud-engine-siblings-v1';
const ENGINE_BODY_SEQ = 27;
const ENGINE_SOURCE_ROOT = 'C:\\Users\\User\\Desktop\\קבצים\\פרויקטים\\Cloud and synchronization';
// חותמת זמן של עדכון גוף אחרון (מתעדכן ב־--publish-engine / apply חבילה)
const ENGINE_UPDATED_AT = '';

// אילו קבצים/תיקיות לא לסנכרן
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',
  '.engine-signing.json', 'engine-package.json', 'cloud.knobs.json', 'cloud.runtime.js',
  'cloud.engine.seal', 'engine-siblings.js', '.cloud-siblings.enc',
];

// ממסרים ציבוריים חופשיים של הרשת (אפשר להוסיף/להחליף)
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'. ריק = בלי שלב בנייה.
// 'AUTO' = זיהוי מ-package.json (scripts.build) בזמן --deploy.
const BUILD_COMMAND = '';

// דומיין משלך (אופציונלי): למשל 'www.myproject.io'. דומיין אמיתי חייב
// להירכש אצל רשם דומיינים (עלות שנתית) — אין דרך חוקית לקבל .io בחינם,
// אצל אף שירות בעולם. אחרי רכישה: מפנים רשומת CNAME מהדומיין אל כתובת
// ה-npub של האתר (מודפסת ב---status) והאתר המבוזר מוגש מהדומיין שלך.
const CUSTOM_DOMAIN = '';

// סיומת וירטואלית של המנוע — לא דומיין DNS רשום ולא התחזות ל-TLD אמיתי.
// יוצרת כתובת קצרה slug.suffix (למשל my-game.io) מעל ה-npub; נפתרת ברשת המבוזרת.
// ריק / 'off' = כבוי. CUSTOM_DOMAIN (אם רכשתם) עדיין עדיף לפתיחה בדפדפן רגיל.
const SKY_SUFFIX = 'io';
const SKY_SUFFIX_ENABLED = true;

// שרתי קבצים ציבוריים מבוזרים (Blossom) שמאחסנים את קבצי האתר.
// הסדר חשוב: שערי האתר פונים אליהם לפי הסדר, והראשונים מגישים
// Content-Type תקין (CSS/JS מוצגים נכון בדפדפן).
const BLOSSOM_SERVERS = [
  'https://cdn.nostrcheck.me',
  'https://nostr.download',
  'https://blossom.primal.net',
  'https://blossom.band',
];

// שערים ציבוריים שמגישים את האתר מהרשת המבוזרת (אותו אתר בכולם).
// אם רשת/ספק חוסם שער אחד — המראות האחרות עדיין עובדות (אותו npub).
// preferredGateway ב-.cloud-state יכול להחליף את הראשון אחרי probe.
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;
// Apparent Physics Lattice: רשת עובדים + CDN + כתובת קצרה מעל npub + SLA
// (לא שוברת Schnorr/Nostr — עוקפת מגבלות מוצר ע"י ארכיטקטורה)
const WORKER_MESH = true;          // heartbeat + peer list ל-SkyDB shards
const WORKER_TTL_MS = 90_000;      // עובד בלי beat נחשב מת
const CDN_LATTICE = true;          // פרסום /cdn.json + בדיקת מראות
const ADDRESS_STACK_AUTO = true;   // handle + calling-card אוטומטי בבוט
// Living Global Address: הכתובת הגלובלית (nsite או CUSTOM_DOMAIN) מתנהגת כ-Node מלא
// דרך Ω mesh + visitor workers + API snapshots + ∇ — לא "Node על השער" (פיזיקה כנה).
const LIVING_ADDRESS = true;
const FULL_APP_STRICT = true;      // לא מפרסמים tip nsite חלקי — רק אפליקציה מלאה
const SIDE_EFFECT_DIAL = true;     // חיבור ייעודי Ω למבקר (side-effects דרך mesh)
const ENFORCE_SHARD_OWNER = true;  // S9 אכיפה כשיש ≥2 peers חיים
// מיזוג נתוני KV/webkv בעת קונפליקט דו־כותב:
// 'smart' = איחוד שדות אובייקט + LWW רק על שדה מתנגש; 'lww' = מחרוזת/ערך שלם מנצח לפי HLC
const KV_MERGE = 'smart';
// SpaceMesh / NASA-grade DTN (כנה: לא משגרים לוויינים מקובץ JS).
// שכבת בקרה: Contact Graph Routing + ריקוד מסלולים + custody store-and-forward
// (מורשת DTN/ION של NASA/JPL) מעל ממסרים קרקעיים + מחלקת "space" לגיוון נתיבים.
// קישוריות לוויינית אמיתית = ISP/gateway חיצוני; כאן המתמטיקה והריקוד בין נתיבים.
const SPACE_MESH = {
  enabled: true,
  slots: 12,                       // קבוצת "מסלולים" לוגית (LEO-inspired)
  periodMs: 5_400_000,             // ~90 דק׳ מחזור LEO (לריקוד handover)
  dancePeriodMs: 90_000,           // תדירות החלפת נתיב פעיל ב-heal
  minElev: 0.12,                   // סף "נראות" (cos anomaly)
  custodyTtlMs: 3_600_000,         // DTN custody
  // חיבור לנתיבי חלל/גיוון — לא שליטה בלוויינים פיזיים.
  // spaceClass = אנטנות/ממסרים שהמנוע מתחבר אליהם; כתובת+שכפולים חיים גם שם.
  spaceClass: [
    'wss://relay.nostr.wirednet.jp',
    'wss://nostr.wine',
    'wss://relay.nostr.net',
  ],
  addressInSpace: true,            // הכתובת הגלובלית מפורסמת גם על נתיבי space
  catalogQuorum: 2,                // S138: מינימום peers שמאשרים sat: לפני שכפול
  hopVaultMin: 3,                  // S139: מינימום hops ל־vault shard
};
// Immortal Self-Replication — הענן חי על הרשת גם כשהמקור כבוי/לא נגיש.
// "אנטנות" = Blossom + nsite gateways + Nostr relays (נקודות אינטרנט ציבוריות).
// "מסלול לווייני" = סיווג נתיב לפי RTT/jitter (ISP לווייני), לא דיבור עם לוויין בודד.
// שכפולים חכמים: tip משותף; כל שכפול מתעדכן כשה-digest משתנה.
const IMMORTAL_MESH = {
  enabled: true,
  minReplicas: 2,                  // מינימום שכפולים חיים נגד הכחדה
  minAntennas: 3,                 // מינימום אנטנות (ממסר/blossom/nsite) עם tip
  satRttMs: 600,                   // מעל זה → חשד לנתיב לווייני/גאו
  satJitterMs: 80,
  probeTimeoutMs: 4_000,
};
// ADO — Autonomous Distributed Organism: כל עותק = מוח + חבר ברשת.
// כנה: לא נשק/בוטנט; תשתית עמידה Zero-Trust על אנטנות ציבוריות.
const ADO = {
  enabled: true,
  minBrains: 2,
  minAntennas: 3,
  stickyGreenProbes: 3,            // S129: יציאה מ-sticky אחרי N probes ירוקים
  replicaLagBoundMs: 120_000,       // S132
  visitorWeightBase: 0.35,
  vaultK: 4,
  vaultN: 8,
  lightPresets: {                  // Interplanetary logical light-delay (ms one-way)
    earth: 40,
    leo: 80,
    luna: 1_280,
    mars: 240_000,
  },
};
// SIOS — Sovereign Internet OS (micro-kernel plugins; not a botnet)
const SIOS = {
  enabled: true,
  plugins: {
    economy: true,
    runtime: true,
    skydrive: true,
    did: true,
    gateway: true,
  },
  creditPerHostSec: 0.001,
  debitPerConsumerSec: 0.0012,
  creditPerMb: 0.0001,             // S141: bandwidth debit על crosslink
  embedDim: 32,
};
// ADIS — Autonomous Distributed Intelligence System (Organic AI Swarm)
// Primary = Cognitive Kernel (math, near-zero weight). SLM/API = fused lobes.
const ADIS = {
  enabled: true,
  slmAuto: false,                  // true = download SLM weights when network ok
  preferMeshInfer: true,
  allowLocal: true,
  maxLocalMb: 512,
  maxApiLobes: 32,
  manifestUrl: null,               // Blossom/SkyDrive SLM manifest
  flockId: null,                   // set at enable from tip/handle
};
// Swarm Mesh S173–S180 — multi-agent apps on same SYNC_ID (not AWS/Celery)
const SWARM_MESH = {
  enabled: true,
  maxAgents: 10_000,
  heartbeatMs: 30_000,
  handoffTtlMs: 300_000,
  toolSigOnlyInPrompt: true,
  contextPruneTopK: 12,
  toolDeprecateMs: 30 * 86_400_000,
};
// ─── Lattice Wave S151–S172 knobs (educational crypto wired into protocol) ───
const BFT_SYNC = false;              // true = require PBFT-lite ≥3 before tip accept
const COVERT_MESH = false;           // optional stego/covert publish path
const NODE_AGENT = false;            // opt-in local agent — NOT distributed by default
const NODE_AGENT_API = '';           // local/API endpoint when NODE_AGENT=true
const HYBRID_LATTICE = true;         // session wrap: AES-GCM(H(ecdh‖latticeShared))
const LATTICE_WAVE = {
  bftF: 1,                           // PBFT f=1 ⇒ need 2f+1=3
  offlineQueueMax: 10_000,
  swarmDecay: 0.92,
  oracleMin: 3,
  siblingPushThrottleMs: 120_000,
};
// S16 Capsule: כשמוגדר ע"י --pack — מטען דחוס של קבצי אפליקציה (null = אין)
const CAPSULE_EMBED = null;

// SkyLattice-Μ / SkyMind — גשר LLM דרך המנוע (זול+חכם):
// Client → ∇ מקומי → דלתא/דחיסה → מטמון SkyDB → API חיצוני (רק ב-miss)
// מפתח מ-ENV בלבד (לא ב-SYNC_ID). תואם OpenAI / Groq / Ollama / LM Studio / כל /v1/chat/completions.
// מקומי (127.0.0.1/localhost): אם אין מפתח — נשלח Bearer "lm-studio" (או SKYCLOUD_LLM_LOCAL_KEY).
const MIND = {
  enabled: false,                    // או --mind-api / configureMind({enabled:true})
  provider: 'openai-compat',         // openai-compat | anthropic | mock
  baseUrl: process.env.SKYCLOUD_LLM_URL || 'https://api.openai.com/v1',
  apiKeyEnv: 'SKYCLOUD_LLM_KEY',     // גם OPENAI_API_KEY כנפילה
  model: process.env.SKYCLOUD_LLM_MODEL || 'gpt-4o-mini',
  cheapModel: process.env.SKYCLOUD_LLM_CHEAP || 'gpt-4o-mini',
  maxTokens: 1024,
  cacheTtlMs: 3_600_000,             // שעת מטמון ברירת מחדל
  budgetUsdPerDay: Number(process.env.SKYCLOUD_LLM_BUDGET || 5),
  usdPer1kIn: 0.00015,               // הערכה ל-governor (מתעדכן ב-configureMind)
  usdPer1kOut: 0.0006,
  system: 'You are a helpful assistant running through SkyCloud Mind Bridge. Be concise.',
};

/** התראות מפעיל (webhook / Telegram) — מעבר ל־CLI. כבוי בלי ENV. */
const ALERTS = {
  enabled: !!(process.env.SKYCLOUD_ALERT_WEBHOOK || process.env.SKYCLOUD_TG_BOT),
  webhookUrl: process.env.SKYCLOUD_ALERT_WEBHOOK || '',
  telegramBotToken: process.env.SKYCLOUD_TG_BOT || '',
  telegramChatId: process.env.SKYCLOUD_TG_CHAT || '',
  minIntervalMs: Number(process.env.SKYCLOUD_ALERT_MIN_MS || 300_000),
  onRelaysDown: true,
  onRelaysWeak: true,
};

// כרייה/תרומת-עיבוד: כבוי כברירת מחדל. הפעלה דורשת הסכמה מפורשת בזמן ריצה
// (--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, spawn } = require('node:child_process');
let engineSiblings = null;
try { engineSiblings = require(path.join(__dirname, 'engine-siblings.js')); } catch {
  try { engineSiblings = require(path.join(__dirname, 'orchestrator', 'engine-siblings.js')); } catch { /* sealed-only copy may embed helpers later */ }
}

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

/** מועמדים לתיקיית אתר (סדר = עדיפות לפלט בנייה) */
const SITE_DIR_CANDIDATES = ['dist', 'build', 'out', 'public', 'www', 'site', 'web', 'docs'];

function findSiteDirWithIndex(root) {
  for (const dir of SITE_DIR_CANDIDATES) {
    if (fs.existsSync(path.join(root, dir, 'index.html'))) return dir;
  }
  if (fs.existsSync(path.join(root, 'index.html'))) return '.';
  return null;
}

/** תיקיית האתר בפועל: מוגדרת ידנית, נפילה חכמה ל־AUTO, או שורש הפרויקט */
function resolveSiteDir() {
  if (SITE_DIR !== 'AUTO') {
    if (fs.existsSync(path.join(ROOT, SITE_DIR, 'index.html'))) return SITE_DIR;
    const auto = findSiteDirWithIndex(ROOT);
    if (auto && auto !== '.') {
      log(`אזהרה: SITE_DIR='${SITE_DIR}' בלי index.html — מגישים מ־"${auto}/" (זיהוי אוטומטי).`);
      return auto;
    }
    if (auto === '.') {
      log(`אזהרה: SITE_DIR='${SITE_DIR}' בלי index.html — מגישים מ־index.html בשורש.`);
      return null; // _publishWebsite: null = קבצי מניפסט / שורש
    }
    log(`אזהרה: SITE_DIR='${SITE_DIR}' לא מכיל index.html — האתר יוגש משורש הפרויקט.`);
    return null;
  }
  const found = findSiteDirWithIndex(ROOT);
  return found && found !== '.' ? found : null;
}

/**
 * פקודת בנייה אפקטיבית: מחרוזת מפורשת, או 'AUTO' / ריק+auto → מ־package.json.
 * כנה: לא מריצים build בלי שיש scripts.build (או אופציונלי build:*) .
 */
function resolveBuildCommand(opts = {}) {
  const raw = opts.command != null ? opts.command : BUILD_COMMAND;
  const wantAuto = raw === 'AUTO' || (opts.auto && !String(raw || '').trim());
  if (!wantAuto && String(raw || '').trim()) return { command: String(raw).trim(), source: 'config' };
  try {
    const pkgPath = path.join(opts.root || ROOT, 'package.json');
    if (!fs.existsSync(pkgPath)) return { command: '', source: 'none', reason: 'no-package-json' };
    const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
    const scripts = pkg.scripts || {};
    if (scripts.build) return { command: 'npm run build', source: 'package.json#scripts.build' };
    const named = Object.keys(scripts).find((k) => /^(build:|vite:build|export)$/i.test(k) || k === 'export');
    if (named) return { command: 'npm run ' + named, source: 'package.json#scripts.' + named };
    return { command: '', source: 'none', reason: 'no-build-script' };
  } catch (err) {
    return { command: '', source: 'error', reason: err.message };
  }
}

/** מזהה כתובת/פורט מלוג npm run dev|start (Vite/Next/CRA וכו') */
function parseDevServerUrlPlan(text = '', opts = {}) {
  const s = String(text || '');
  const urls = [];
  const portRe = [
    /https?:\/\/(?:127\.0\.0\.1|localhost|\[::1\]|0\.0\.0\.0):(\d{2,5})/gi,
    /ready - started server on .+:(\d{2,5})/gi,
    /Local:\s*(https?:\/\/[^\s]+)/gi,
    /on\s+(?:port|Port)\s+(\d{2,5})/gi,
    /listening on.*?:(\d{2,5})/gi,
  ];
  for (const re of portRe) {
    let m;
    while ((m = re.exec(s))) {
      if (m[1] && /^\d+$/.test(m[1])) {
        const port = Number(m[1]);
        if (port > 0 && port < 65536) urls.push(`http://127.0.0.1:${port}`);
      } else if (m[1] && /^https?:\/\//.test(m[1])) {
        urls.push(String(m[1]).replace(/[.,)]+$/, ''));
      }
    }
  }
  const preferred = urls.find((u) => /:5173|:3000|:8080|:4173/.test(u)) || urls[0] || null;
  const port = preferred ? Number((preferred.match(/:(\d+)\/?$/) || [])[1]) : null;
  return { ok: !!preferred, url: preferred, port, candidates: [...new Set(urls)].slice(0, 8) };
}

function devServerScriptPlan(pkg) {
  const scripts = pkg?.scripts || {};
  if (scripts.dev) return { command: 'npm run dev', script: 'dev' };
  if (scripts.start) return { command: 'npm run start', script: 'start' };
  return { command: '', script: null };
}

function patchCloudKnobFile(filePath, name, value) {
  try {
    let text = fs.readFileSync(filePath, 'utf8');
    const re = new RegExp(`(const\\s+${name}\\s*=\\s*)(['"\`][^'"\`]*['"\`]|true|false|\\d+)`);
    if (!re.test(text)) return false;
    const lit = typeof value === 'string' ? `'${String(value).replace(/'/g, "\\'")}'` : String(value);
    text = text.replace(re, `$1${lit}`);
    fs.writeFileSync(filePath, text, 'utf8');
    return true;
  } catch { return false; }
}

function treeKillProcess(pid) {
  if (!pid) return;
  if (process.platform === 'win32') {
    spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
    return;
  }
  try { process.kill(-pid, 'SIGTERM'); } catch {
    try { process.kill(pid, 'SIGTERM'); } catch { /* ignore */ }
  }
}

function runShellCommand(cmd, opts = {}) {
  const timeoutMs = opts.timeoutMs ?? 300_000;
  return new Promise((resolve) => {
    const isWin = process.platform === 'win32';
    const child = spawn(isWin ? 'cmd.exe' : 'sh', isWin ? ['/c', cmd] : ['-c', cmd], {
      cwd: opts.cwd || ROOT,
      env: { ...process.env, ...(opts.env || {}), FORCE_COLOR: '0', CI: 'true', BROWSER: 'none' },
      windowsHide: true,
      detached: !isWin,
    });
    let stdout = '';
    let stderr = '';
    const timer = setTimeout(() => {
      try { treeKillProcess(child.pid); } catch {}
      resolve({ ok: false, code: 1, stdout, stderr, reason: 'timeout' });
    }, timeoutMs);
    child.stdout?.on('data', (d) => { stdout += d.toString(); });
    child.stderr?.on('data', (d) => { stderr += d.toString(); });
    child.on('error', (err) => {
      clearTimeout(timer);
      resolve({ ok: false, code: 1, stdout, stderr, reason: err.message });
    });
    child.on('close', (code) => {
      clearTimeout(timer);
      resolve({ ok: code === 0, code: code ?? 1, stdout, stderr });
    });
  });
}

function projectPublishPipelinePlan(root = ROOT, opts = {}) {
  const publish = inferProjectPublishPlan(root, opts);
  let pkg = null;
  try { pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); } catch {}
  const dev = devServerScriptPlan(pkg);
  return {
    ok: true,
    phases: ['extract', 'install', 'build', 'dev-probe', 'deploy', 'stop-dev'],
    publish,
    dev,
    hasPackage: !!pkg,
    note: 'dev-probe זמני לזיהוי פורט/API — השרת נסגר לפני פרסום; LOCAL_BRIDGE נשמר ב-cloud.js',
  };
}

/**
 * תוכנית פרסום חכמה לסוכן/מפעיל — מה צריך כדי שהכתובת הגלובלית תציג
 * את האפליקציה המלאה + איך מחברים סנכרון משתמשי קצה / אריזת EXE·APK.
 * לא משנה קבצים; רק מסיק וממליץ.
 */
function inferProjectPublishPlan(root = ROOT, opts = {}) {
  const rootAbs = root || ROOT;
  const siteConfigured = opts.siteDir != null ? opts.siteDir : SITE_DIR;
  const siteResolved = (() => {
    if (siteConfigured && siteConfigured !== 'AUTO') {
      if (fs.existsSync(path.join(rootAbs, siteConfigured, 'index.html'))) return siteConfigured;
    }
    return findSiteDirWithIndex(rootAbs);
  })();
  const bat = (() => {
    try {
      if (rootAbs === ROOT) return resolveBatEntry();
    } catch {}
    return null;
  })();
  const build = resolveBuildCommand({ root: rootAbs, command: opts.buildCommand != null ? opts.buildCommand : BUILD_COMMAND, auto: true });
  let pkg = null;
  try { pkg = JSON.parse(fs.readFileSync(path.join(rootAbs, 'package.json'), 'utf8')); } catch {}
  const hasCloudWebHint = true; // engine always injects /cloud-web.js on living publish
  const publicWebsite = opts.publicWebsite != null ? !!opts.publicWebsite : !!PUBLIC_WEBSITE;
  const fullStrict = opts.fullAppStrict != null ? !!opts.fullAppStrict : !!FULL_APP_STRICT;
  const living = opts.livingAddress != null ? !!opts.livingAddress : !!LIVING_ADDRESS;
  const needsBridge = !!(pkg?.scripts?.start || pkg?.scripts?.dev || opts.localBridge || LOCAL_BRIDGE);
  const steps = [];
  if (!publicWebsite) steps.push({ op: 'set', key: 'PUBLIC_WEBSITE', value: true, why: 'global URL must publish site shell' });
  if (siteConfigured === 'site' && siteResolved && siteResolved !== 'site' && siteResolved !== '.') {
    steps.push({ op: 'set', key: 'SITE_DIR', value: siteResolved, why: 'index.html found under ' + siteResolved + '/' });
  }
  if (siteConfigured === 'AUTO' || !siteResolved) {
    steps.push({ op: 'ensure', key: 'SITE_DIR', value: siteResolved || 'AUTO', why: siteResolved ? 'auto-detected site root' : 'no index.html yet — build first or add site/' });
  }
  if (build.command && !(BUILD_COMMAND || '').trim()) {
    steps.push({ op: 'set', key: 'BUILD_COMMAND', value: build.command, why: build.source });
  }
  if (needsBridge && !(LOCAL_BRIDGE || '').trim() && !FULL_NODE) {
    steps.push({ op: 'optional', key: 'LOCAL_BRIDGE or --full-node', value: 'http://127.0.0.1:<port>', why: 'dynamic server (Next/Express) behind global /api' });
  }
  steps.push({ op: 'run', cmd: 'node cloud.js --deploy', why: 'build (if any) + full-app nsite tip + private sync' });
  steps.push({ op: 'verify', cmd: 'node cloud.js --status', why: 'same address + SITE_DIR announcement' });

  const endUserSync = {
    browser: {
      sdk: '/cloud-web.js',
      sharedPublic: 'SkyCloud.save / webSet (public channel — no secrets)',
      perUserPrivate: 'SkyCloud.auth + userSet/userGet (AES per user)',
      live: 'onChange / live poll — refresh or live update when peers online',
    },
    nativeApkExe: {
      patternA: 'WebView → global URL (site already full) + same SDK over HTTPS',
      patternB: 'Embed cloud.js sidecar (Node) or packCapsule --keep-id for desktop',
      patternC: 'Store SYNC_ID in secure storage only on operator/dev builds — end-user apps usually use visitor auth + userSet, not the project SYNC_ID',
      ux: 'GET /api/ux/sync for RELAYS_* human messages',
      honest: 'cloud.js does not compile APK/EXE; wire Capacitor/WebView/installer to URL or capsule',
    },
  };

  const dataContinuity = {
    privateCloud: 'Existing encrypted files/KV on this SYNC_ID stay; --deploy syncs local↔network (LWW on conflicts)',
    publicSite: 'New full tip replaces site paths only after FULL_APP_STRICT gate; blobs content-addressed',
    never: 'Do not --new-id if prior data must remain at this address',
  };

  const liveUpdate = {
    whileRunning: 'node cloud.js (live) republishes site on change when PUBLIC_WEBSITE',
    afterBuild: 'node cloud.js --deploy',
    visitors: 'Gateway cache may lag minutes — try mirror; soft refresh. Live KV via SDK when mesh/relays reachable.',
  };

  const ready = !!(publicWebsite && (siteResolved || bat) && fullStrict);
  return {
    ok: true,
    ready,
    root: rootAbs,
    siteConfigured,
    siteResolved: siteResolved === '.' ? null : siteResolved,
    rootIndex: siteResolved === '.' || fs.existsSync(path.join(rootAbs, 'index.html')),
    batEntry: bat,
    build,
    knobs: {
      PUBLIC_WEBSITE: publicWebsite,
      FULL_APP_STRICT: fullStrict,
      LIVING_ADDRESS: living,
      LOCAL_BRIDGE: opts.localBridge || LOCAL_BRIDGE || '',
      FULL_NODE: !!FULL_NODE,
    },
    injectsOnPublish: ['/cloud-web.js', '/skylive.json'],
    hasCloudWebHint,
    steps,
    endUserSync,
    dataContinuity,
    liveUpdate,
    packaging: endUserSync.nativeApkExe,
    native: null, // filled by caller via inferNativeClientPlan when needed
    agentRule: 'Follow AI-AGENT-GUIDE §2.7–§2.8 — inferProjectPublishPlan + inferNativeClientPlan / --status → --deploy; embed/update via --embed-client / --client-update; keep FULL_APP_STRICT; preserve SYNC_ID.',
  };
}

/** סריקת עומק רדודה לקבצי התקנה / פלטפורמות מקוריות */
function walkNativeArtifacts(root, opts = {}) {
  const maxDepth = opts.maxDepth ?? 3;
  const maxFiles = opts.maxFiles ?? 80;
  const out = [];
  const skip = new Set(['node_modules', '.git', '.cloud', 'dist', 'build', '.sky-unpacked']);
  const re = /\.(apk|aab|exe|msi|msix|dmg|appimage)$/i;
  const walk = (dir, depth) => {
    if (out.length >= maxFiles || depth > maxDepth) return;
    let entries = [];
    try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
    for (const e of entries) {
      if (out.length >= maxFiles) break;
      if (e.name.startsWith('.') && e.name !== '.') continue;
      if (skip.has(e.name)) continue;
      const full = path.join(dir, e.name);
      if (e.isDirectory()) walk(full, depth + 1);
      else if (e.isFile() && re.test(e.name)) {
        let st = null;
        try { st = fs.statSync(full); } catch {}
        out.push({
          kind: path.extname(e.name).slice(1).toLowerCase(),
          name: e.name,
          rel: path.relative(root, full).replace(/\\/g, '/'),
          size: st?.size || 0,
          mtime: st ? Math.floor(st.mtimeMs) : 0,
        });
      }
    }
  };
  walk(root, 0);
  return out;
}

/** זיהוי מעטפות מקוריות בפרויקט (Capacitor/Electron/Android…) */
function detectNativeProjectShells(root) {
  const shells = [];
  const hit = (rel, kind) => {
    if (fs.existsSync(path.join(root, rel))) shells.push({ kind, path: rel });
  };
  hit('capacitor.config.json', 'capacitor');
  hit('capacitor.config.ts', 'capacitor');
  hit('android', 'android-project');
  hit('ios', 'ios-project');
  hit('electron', 'electron');
  hit('electron-builder.yml', 'electron-builder');
  hit('electron-builder.yaml', 'electron-builder');
  hit('src-tauri', 'tauri');
  hit('tauri.conf.json', 'tauri');
  try {
    const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
    const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
    if (deps['@capacitor/core']) shells.push({ kind: 'capacitor-dep', path: 'package.json' });
    if (deps.electron) shells.push({ kind: 'electron-dep', path: 'package.json' });
    if (deps['@tauri-apps/api']) shells.push({ kind: 'tauri-dep', path: 'package.json' });
  } catch {}
  return shells;
}

/** זיהוי מסך/זרימת כניסה באפליקציה — כדי לחבר סנכרון אחרי login */
function detectAppLoginSurface(root, opts = {}) {
  const site = opts.siteDir || findSiteDirWithIndex(root) || 'site';
  const bases = [root, path.join(root, site === '.' ? '' : site)].filter(Boolean);
  const signals = [];
  const patterns = [
    { re: /SkyCloud\.auth|userSet|userGet/i, kind: 'skycloud-auth' },
    { re: /login|signIn|sign-in|auth\(/i, kind: 'login-ui' },
    { re: /firebase\.auth|supabase\.auth|clerk/i, kind: 'third-party-auth' },
  ];
  const scanFile = (full, rel) => {
    if (!/\.(html?|jsx?|tsx?|vue|svelte)$/i.test(full)) return;
    let text = '';
    try { text = fs.readFileSync(full, 'utf8').slice(0, 80_000); } catch { return; }
    for (const p of patterns) {
      if (p.re.test(text)) signals.push({ kind: p.kind, file: rel });
    }
  };
  for (const base of bases) {
    if (!fs.existsSync(base)) continue;
    let entries = [];
    try { entries = fs.readdirSync(base, { withFileTypes: true }); } catch { continue; }
    for (const e of entries.slice(0, 40)) {
      if (!e.isFile()) continue;
      scanFile(path.join(base, e.name), path.relative(root, path.join(base, e.name)).replace(/\\/g, '/'));
    }
    // עומק 1 בתיקיות נפוצות
    for (const sub of ['src', 'js', 'app', 'pages', 'components']) {
      const d = path.join(base, sub);
      if (!fs.existsSync(d)) continue;
      let subE = [];
      try { subE = fs.readdirSync(d, { withFileTypes: true }); } catch { continue; }
      for (const e of subE.slice(0, 30)) {
        if (e.isFile()) scanFile(path.join(d, e.name), path.relative(root, path.join(d, e.name)).replace(/\\/g, '/'));
      }
    }
  }
  const uniq = [];
  const seen = new Set();
  for (const s of signals) {
    const k = s.kind + ':' + s.file;
    if (seen.has(k)) continue;
    seen.add(k);
    uniq.push(s);
  }
  return {
    hasLogin: uniq.some((s) => s.kind === 'login-ui' || s.kind === 'skycloud-auth' || s.kind === 'third-party-auth'),
    hasSkyCloudAuth: uniq.some((s) => s.kind === 'skycloud-auth'),
    signals: uniq.slice(0, 24),
  };
}

/**
 * אחרי כניסה באפליקציה — מה לסנכרן (ענן מיוחד / משתמש / UX).
 * כנה: לא מחליף את UI הלוגין; מגדיר חוזה ל־WebView/sidecar.
 */
function clientLoginSyncGate(opts = {}) {
  const hasLogin = !!opts.hasLogin;
  const userId = opts.userId || opts.uid || null;
  if (!hasLogin && !userId) {
    return {
      ok: true, mode: 'anonymous-visitor',
      actions: ['pull-public-site', 'optional-SkyCloud.save-public'],
      note: 'No login surface — public site + optional public channel only',
    };
  }
  return {
    ok: true,
    mode: 'post-login-sync',
    userId,
    actions: [
      'SkyCloud.auth.ensure',
      'userSet/userGet private namespace',
      'GET /api/ux/sync',
      'GET /api/client/update (installer channel)',
      'pull living /api when mesh available',
    ],
    projectSyncId: opts.embedProjectSync === true,
    warning: opts.embedProjectSync
      ? 'Embedding project SYNC_ID in store APK exposes private cloud — prefer visitor auth'
      : null,
    api: { ux: '/api/ux/sync', update: '/api/client/update', loginSync: '/api/client/login-sync' },
  };
}

/** ייבוא נתונים מקומיים קיימים → מפתחות ענן (תוכנית + רשימת קבצים) */
function localDataImportPlan(root, opts = {}) {
  const dirs = opts.dirs || ['data', 'local', 'userdata', 'storage', '.data'];
  const files = [];
  for (const d of dirs) {
    const base = path.join(root, d);
    if (!fs.existsSync(base)) continue;
    let entries = [];
    try { entries = fs.readdirSync(base, { withFileTypes: true }); } catch { continue; }
    for (const e of entries) {
      if (!e.isFile()) continue;
      if (!/\.(json|txt|csv|db|sqlite)$/i.test(e.name)) continue;
      const full = path.join(base, e.name);
      let st = null;
      try { st = fs.statSync(full); } catch {}
      if (st && st.size > (opts.maxBytes || 2_000_000)) continue;
      files.push({
        rel: (d + '/' + e.name).replace(/\\/g, '/'),
        key: 'localimport:' + d + ':' + e.name.replace(/\W+/g, '_'),
        size: st?.size || 0,
      });
    }
  }
  return {
    ok: true,
    n: files.length,
    files: files.slice(0, opts.limit || 200),
    policy: 'cloud.set JSON/text; binary as base64 meta; LWW if key exists',
    note: 'Does not delete local originals; merge is last-write-wins on conflict',
  };
}

/** מניפסט עדכון ללקוחות מותקנים (APK/EXE) — נפרד מפרסום הכתובת */
function clientUpdateManifestPlan(parts = {}) {
  const artifacts = Array.isArray(parts.artifacts) ? parts.artifacts : [];
  const man = {
    v: 1,
    kind: 'skycloud-client-update',
    version: String(parts.version || parts.semver || '0.0.0'),
    buildId: parts.buildId || hex(sha256(Buffer.from('client-upd:' + (parts.version || '') + ':' + (parts.tipDigest || '') + ':' + Date.now()))).slice(0, 24),
    tipDigest: parts.tipDigest || null,
    siteAddress: parts.siteAddress || null,
    channel: parts.channel || 'stable',
    artifacts: artifacts.map((a) => ({
      kind: a.kind, name: a.name, rel: a.rel, size: a.size,
      sha256: a.sha256 || null, url: a.url || null,
    })),
    living: {
      notStaticOnly: true,
      requireCloudWeb: true,
      fullAppStrict: parts.fullAppStrict !== false,
    },
    loginSync: parts.loginSync || null,
    publishedAt: parts.now || Date.now(),
  };
  man.digest = hex(sha256(Buffer.from('client-update:v1:' + xiCanon({
    version: man.version, buildId: man.buildId, tip: man.tipDigest, arts: man.artifacts.map((a) => a.sha256 || a.rel),
  })))).slice(0, 32);
  return { ok: true, manifest: man };
}

/**
 * תוכנית לקוח מקורי מלאה: זיהוי APK/EXE + הטמעה מאחורי הקלעים +
 * סנכרון אחרי login + ערוץ עדכונים מהפרויקט הראשי + ייבוא מקומי.
 * כנה: המנוע לא קומפל APK/EXE — מפרסם מניפסט, מעלה ארטיפקטים קיימים, ומייצר scaffold.
 */
function inferNativeClientPlan(root = ROOT, opts = {}) {
  const rootAbs = root || ROOT;
  const publish = inferProjectPublishPlan(rootAbs, opts);
  const artifacts = walkNativeArtifacts(rootAbs, opts);
  const shells = detectNativeProjectShells(rootAbs);
  const login = detectAppLoginSurface(rootAbs, { siteDir: publish.siteResolved || SITE_DIR });
  const local = localDataImportPlan(rootAbs, opts);
  const loginGate = clientLoginSyncGate({
    hasLogin: login.hasLogin || login.hasSkyCloudAuth,
    embedProjectSync: !!opts.embedProjectSync,
  });
  const livingNotStatic = {
    ok: !!(publish.knobs.LIVING_ADDRESS && publish.knobs.FULL_APP_STRICT && publish.knobs.PUBLIC_WEBSITE),
    means: 'nsite tip + /cloud-web.js + Ω mesh/bridge/snapshots — not static-only file dump',
    needWorker: !!(publish.knobs.LOCAL_BRIDGE || publish.knobs.FULL_NODE || opts.keepNodeRunning),
  };
  const steps = [
    { op: 'run', cmd: 'node cloud.js --status', why: 'see publish + native plan' },
    { op: 'run', cmd: 'node cloud.js --embed-client', why: 'write skycloud-native.json + bridge stub into project' },
    { op: 'run', cmd: 'node cloud.js --deploy', why: 'full site + private sync (not partial)' },
  ];
  if (local.n) steps.push({ op: 'run', cmd: 'node cloud.js --import-local', why: 'sync existing local data/ files into cloud.set' });
  if (artifacts.length) {
    steps.push({ op: 'run', cmd: 'node cloud.js --client-update', why: 'publish installer update channel (APK/EXE blobs + manifest)' });
  } else if (shells.length) {
    steps.push({ op: 'note', why: 'native shell detected — build APK/EXE with your toolchain, place artifact in project, then --client-update' });
  }
  steps.push({ op: 'wire', why: 'On app login call POST /api/client/login-sync then pull userSet + /api/client/update' });

  return {
    ok: true,
    ready: !!(publish.ready && livingNotStatic.ok),
    publish,
    artifacts,
    shells,
    login,
    loginGate,
    localImport: local,
    livingNotStatic,
    dualChannel: {
      address: 'node cloud.js --deploy → global URL (everyone refreshes site)',
      installers: 'node cloud.js --client-update → /api/client/update (APK/EXE check for new build)',
      note: 'Channels are independent: can ship site without new EXE, or EXE without site tip change',
    },
    embed: {
      files: ['skycloud-native.json', 'native/skycloud-bridge.js'],
      behindTheScenes: true,
      onInstall: 'bridge reads native.json → opens living URL or sidecar; on login → login-sync gate',
    },
    steps,
    honest: {
      compilesApkExe: false,
      uploadsExistingArtifacts: true,
      fullAppNotPartial: !!publish.knobs.FULL_APP_STRICT,
      livingNotStaticOnly: livingNotStatic.ok,
    },
    agentRule: 'AI-AGENT-GUIDE §2.8 — embed-client, import-local, deploy, client-update; wire login→/api/client/login-sync; never claim engine compiles store binaries.',
  };
}

/**
 * איך מחברים לפרויקט קיים: קובץ / תיקייה / מידע / ZIP / פרויקט שלם.
 * scope: 'file' | 'folder' | 'data' | 'zip' | 'whole' | 'auto'
 * כנה: סנכרון פרטי של חלק מהקבצים = תקין; tip אתר חלקי תחת FULL_APP_STRICT = אסור.
 */
function inferConnectScopePlan(scope = 'auto', opts = {}) {
  const root = opts.root || ROOT;
  const s = String(scope || 'auto').toLowerCase();
  const publish = inferProjectPublishPlan(root, opts);
  const local = localDataImportPlan(root, opts);
  let mode = s;
  if (mode === 'auto') {
    if (opts.zipPath) mode = 'zip';
    else if (opts.onlyData || (local.n > 0 && opts.preferData)) mode = 'data';
    else if (opts.relFile) mode = 'file';
    else if (opts.relFolder) mode = 'folder';
    else mode = 'whole';
  }
  const base = {
    ok: true, mode, root, syncIdMustStay: true,
    layers: {
      privateCloud: 'project files under root except IGNORE → relays (encrypted)',
      publicSite: 'SITE_DIR (+ build) → Blossom + nsite tip (FULL_APP_STRICT)',
      kvData: 'cloud.set / --import-local / userSet — not the same as file sync',
    },
    never: [
      'Do not --new-id when joining an existing cloud/address',
      'Do not disable FULL_APP_STRICT to publish a half site without user ask',
      'Do not invent S3/FTP/Firebase upload',
    ],
  };

  if (mode === 'file') {
    const rel = opts.relFile || '<path/to/file>';
    return {
      ...base,
      intentHe: 'חיבור קובץ/מידע בודד לענן הפרטי',
      steps: [
        { op: 'place', what: rel, where: 'project root (not in IGNORE)' },
        { op: 'run', cmd: 'node cloud.js --once', why: 'upload that file (+ pull others)' },
        { op: 'optional', cmd: 'node cloud.js --deploy', why: 'only if the file is part of the public SITE_DIR UI' },
      ],
      addressUpdates: 'Only if file is under SITE_DIR and you --deploy (full site tip)',
    };
  }
  if (mode === 'folder') {
    const rel = opts.relFolder || '<folder>';
    return {
      ...base,
      intentHe: 'חיבור תיקייה/חלק מהפרויקט',
      steps: [
        { op: 'place', what: rel, where: 'under project root with cloud.js' },
        { op: 'optional', key: 'IGNORE', value: 'add sibling folders you must NOT sync', why: 'narrow private sync' },
        { op: 'optional', key: 'SITE_DIR', value: rel, why: 'if this folder IS the public website root (has index.html)' },
        { op: 'run', cmd: 'node cloud.js --once', why: 'private sync for files in tree' },
        { op: 'run', cmd: 'node cloud.js --deploy', why: 'rebuild+publish full public tip when site should update' },
      ],
      addressUpdates: 'Global URL follows SITE_DIR full tip after --deploy — not a partial path publish',
    };
  }
  if (mode === 'data') {
    return {
      ...base,
      intentHe: 'ייבוא מידע מקומי (JSON/DB) לענן בלי בהכרח לפרסם אתר',
      steps: [
        { op: 'ensure', what: 'data/ or local/ or userdata/ files', n: local.n },
        { op: 'run', cmd: 'node cloud.js --import-local', why: 'cloud.set localimport:*' },
        { op: 'optional', cmd: 'node cloud.js --once', why: 'also sync ordinary project files' },
        { op: 'optional', cmd: 'node cloud.js --deploy', why: 'if UI that shows this data must refresh on the address' },
      ],
      addressUpdates: 'KV import alone does not change nsite tip; --deploy does',
      localImport: local,
    };
  }
  if (mode === 'zip') {
    const z = opts.zipPath || 'system.zip';
    return {
      ...base,
      intentHe: 'ייבוא מערכת/פרויקט מ-ZIP ואז חיבור מלא לכתובת',
      steps: [
        { op: 'run', cmd: 'node cloud.js --import ' + z, why: 'unpack into project root' },
        { op: 'run', cmd: 'node cloud.js --status', why: 'auto SITE_DIR / BUILD hints' },
        { op: 'set', key: 'BUILD_COMMAND', value: publish.build.command || 'AUTO', why: 'if package has build' },
        { op: 'run', cmd: 'node cloud.js --deploy', why: 'build + private sync + full living address' },
      ],
      addressUpdates: 'Same SYNC_ID → same address; new files merge LWW',
      publish,
    };
  }
  // whole
  return {
    ...base,
    mode: 'whole',
    intentHe: 'חיבור כל הפרויקט לענן + כתובת חיה מלאה',
    steps: [
      { op: 'ensure', what: 'cloud.js in project root with SYNC_ID (or first-run AUTO)' },
      { op: 'run', cmd: 'node cloud.js --status', why: 'infer SITE_DIR / BUILD / native' },
      { op: 'set', key: 'SITE_DIR', value: publish.siteResolved || SITE_DIR, why: 'folder with real index.html' },
      { op: 'set', key: 'BUILD_COMMAND', value: publish.build.command || BUILD_COMMAND || '', why: 'rebuild before publish when needed' },
      { op: 'optional', cmd: 'node cloud.js --import-local', why: 'if data/local exists' },
      { op: 'run', cmd: 'node cloud.js --deploy', why: 'full private sync + full-app public tip + address' },
      { op: 'verify', cmd: 'open global URL + one mirror', why: 'complete app, not file listing' },
    ],
    addressUpdates: '--deploy refreshes living URL for everyone (gateway cache may lag minutes)',
    rebuild: 'Change sources → --deploy (BUILD_COMMAND/AUTO runs first)',
    publish,
    agentRule: 'AI-AGENT-GUIDE §2.9 — pick scope file|folder|data|zip|whole; private partial OK; public tip must be full.',
  };
}

/**
 * זיהוי קובץ הפעלה (.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');
/** דוחה Zip Slip: `..`, נתיב מוחלט, או כונן Windows */
function zipEntrySafe(name) {
  const n = String(name || '').replace(/\\/g, '/');
  if (!n) return false;
  if (n.startsWith('/') || /^[a-zA-Z]:/.test(n)) return false;
  return !n.split('/').some((seg) => seg === '..');
}
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; // תיקייה
    if (!zipEntrySafe(name)) throw new Error(`ZIP: נתיב לא בטוח (${name})`);
    // כותרת מקומית — אורך שם/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();

/** S159 toy FHE — must precede XI_OPS.blindSum (TDZ guard) */
function fheEncSimple(key, m) {
  const k = BigInt('0x' + hex(sha256(Buffer.from('fhe-k:' + key))).slice(0, 12));
  const M = BigInt(m);
  const r = BigInt('0x' + hex(crypto.randomBytes(6)).slice(0, 12));
  return { ok: true, ct: { c: (M + k * r).toString(), r: r.toString() }, scheme: 'additive-toy' };
}
function fheAddSimple(a, b) {
  return { ok: true, ct: { c: (BigInt(a.ct.c) + BigInt(b.ct.c)).toString(), r: (BigInt(a.ct.r) + BigInt(b.ct.r)).toString() } };
}

/** רישום אופרטורים טהורים בלבד — אין 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 לא ידוע');
    }
  },
  // S155: FFT קל (radix-2 על וקטור ממשי → מגניטודות) — דטרמיניסטי, חסום
  fftLite: ({ vec }) => {
    if (!Array.isArray(vec) || vec.length < 2 || vec.length > 64) throw new Error('Ξ fftLite: vec לא תקין');
    let n = 1; while (n < vec.length) n *= 2;
    if (n > 64) throw new Error('Ξ fftLite: padded גדול מדי');
    const re = new Array(n).fill(0), im = new Array(n).fill(0);
    for (let i = 0; i < vec.length; i++) re[i] = Number(vec[i]) || 0;
    for (let s = 1; s <= Math.log2(n); s++) {
      const m = 1 << s, m2 = m >> 1;
      const theta = -2 * Math.PI / m;
      for (let k = 0; k < n; k += m) {
        for (let j = 0; j < m2; j++) {
          const tRe = Math.cos(theta * j) * re[k + j + m2] - Math.sin(theta * j) * im[k + j + m2];
          const tIm = Math.sin(theta * j) * re[k + j + m2] + Math.cos(theta * j) * im[k + j + m2];
          re[k + j + m2] = re[k + j] - tRe; im[k + j + m2] = im[k + j] - tIm;
          re[k + j] += tRe; im[k + j] += tIm;
        }
      }
    }
    return re.map((r, i) => Math.hypot(r, im[i]));
  },
  // S155: צעד גרדיאנט ליניארי יחיד — w := w - lr * x * (dot(w,x)-y)
  gradStep: ({ w, x, y, lr = 0.01 }) => {
    if (!Array.isArray(w) || !Array.isArray(x) || w.length !== x.length || w.length > 64) throw new Error('Ξ gradStep: קלט לא תקין');
    const dot = w.reduce((s, wi, i) => s + wi * x[i], 0);
    const err = dot - Number(y);
    return w.map((wi, i) => wi - lr * err * x[i]);
  },
  // S159: blindSum — מחזיר ciphertext toy (מפתח ב-args.key)
  blindSum: ({ values, key = 'fhe-demo' }) => {
    if (!Array.isArray(values) || values.length > 64) throw new Error('Ξ blindSum: values');
    let acc = fheEncSimple(key, Number(values[0]) || 0);
    for (let i = 1; i < values.length; i++) acc = fheAddSimple(acc, fheEncSimple(key, Number(values[i]) || 0));
    return { ct: acc.ct, scheme: 'additive-toy', honesty: 'toy-fhe' };
  },
};

/** קנוניזציה יציבה של 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;
    const author = r.author ?? r.result_author ?? r.solver;
    if (!author) 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(String(author));
  }
  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;
}

async function xiExpandPolicyInValue(val, fetchPolicy) {
  if (val == null || typeof val !== 'object') return val;
  if (typeof val.$policy === 'string') {
    const pol = await fetchPolicy(val.$policy);
    if (!pol?.dag) throw new Error('∇ nested: מדיניות חסרה "' + val.$policy + '"');
    const sub = await xiClientEval(pol.dag, val.inputs || {});
    return sub.output;
  }
  if (val.$dag && typeof val.$dag === 'object') {
    const sub = await xiClientEval(val.$dag, val.inputs || {});
    return sub.output;
  }
  if (Array.isArray(val)) {
    const out = [];
    for (const item of val) out.push(await xiExpandPolicyInValue(item, fetchPolicy));
    return out;
  }
  const out = {};
  for (const k of Object.keys(val)) out[k] = await xiExpandPolicyInValue(val[k], fetchPolicy);
  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 };
    } else if (n.op) {
      nodes[id] = {
        ...n,
        args: await xiExpandPolicyInValue(n.args, fetchPolicy),
        branch: n.branch ? await xiExpandPolicyInValue(n.branch, fetchPolicy) : n.branch,
        coalesce: n.coalesce ? await xiExpandPolicyInValue(n.coalesce, fetchPolicy) : n.coalesce,
      };
    }
  }
  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;
    }
    if ((m = line.match(/^(\w+)\s*=\s*branch\s+(.+)$/i))) {
      const id = m[1]; let rest = m[2].trim();
      const { whenExpr, where } = parseWhen(rest);
      rest = rest.replace(/\bwhen\b.*/, '').replace(/\bwhere\b.*/, '').trim();
      const branchM = rest.match(/^(.+?)\s*\{\s*(.+?)\s*\}\s*default\s*:\s*(\w+)$/i);
      if (!branchM) throw new Error('DSL: branch syntax — branch <on> { k:ref, ... } default:ref');
      const cases = {};
      for (const part of branchM[2].split(',')) {
        const [k, ref] = part.split(':').map((x) => x.trim());
        if (k && ref) cases[k] = { $ref: ref };
      }
      const node = { branch: { on: branchM[1].trim(), vars: where, cases, default: { $ref: branchM[3] } } };
      if (whenExpr) node.when = { expr: whenExpr, vars: { ...where } };
      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;
    const author = r.author || hex(sha256(Buffer.from('screport:' + xiCanon({ pathDigest: r.pathDigest, taken: r.taken, skipped: r.skipped })))).slice(0, 16);
    if (!by.has(r.pathDigest)) by.set(r.pathDigest, { pathDigest: r.pathDigest, authors: new Set(), sample: r });
    by.get(r.pathDigest).authors.add(author);
  }
  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;
  }

  /** S12: מפנה ל-Cloud.applySchemaIntent כשיש cloud; אחרת מחשב diff מקומי */
  async applySchemaIntent(diff, newerNodes = {}) {
    if (this.cloud?.applySchemaIntent) return this.cloud.applySchemaIntent(diff, newerNodes);
    const prev = this._schema || {};
    const asDag = {
      nodes: Object.fromEntries(Object.entries(prev).map(([k, v]) => [k, { value: v }])),
      output: Object.keys(prev)[0] || null,
    };
    const nextDag = dagApplyDiff(asDag, diff, { ...newerNodes, __output: newerNodes.__output });
    const next = {};
    for (const [k, n] of Object.entries(nextDag.nodes || {})) next[k] = n.value;
    this._schema = next;
    return { schema: next, diff };
  }

  _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) {
    // S13/S2: freeze כתיבות אחרי fork עד force
    if (this._freezeWrites && !opts.force) {
      throw new Error('SkyDB S13: כתיבות מוקפאות אחרי logic_fork — העבר {force:true} או בטל pin');
    }
    // S108 Canary SQL Read-Only — עד promote
    if (this.cloud && !opts.force) {
      const cMeta = this.cloud.webGet?.('capsule:canary') || this.cloud.state?.capsuleCanary || null;
      const ro = canarySqlReadOnlyGate(cMeta, this.author || DEVICE, opts);
      if (ro.readOnly) {
        throw new Error('SkyDB S108: canary shard read-only עד promote (' + ro.reason + ')');
      }
    }
    // S9: אכיפת בעלות shard כשיש peers (אופציונלי)
    if (opts.enforceOwner || this._enforceOwner) {
      const own = this.shardOf(key);
      if (own.owner && this.author && own.owner !== this.author && own.owner !== DEVICE) {
        throw new Error('SkyDB S9: המפתח שייך ל-shard של peer אחר (' + own.owner + ')');
      }
    }
    // S12: אכיפת סכמה
    const schema = this._schema || this.cloud?.state?.dbSchema;
    const sv = skydbValidateSchema(schema, ns, key, value);
    if (!sv.ok) throw new Error('SkyDB S12: סכמה נדחתה — ' + sv.reason);
    if (sv.coerced !== undefined) opts._coerced = sv.coerced;
    // S11: receipt-gated writes + ledger נגד שימוש כפול
    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: חתימת-קבלה נדחתה');
      }
      const rid = signed.id;
      if (rid && this._spentReceipts?.has(rid) && !opts.allowReplay) {
        throw new Error('SkyDB S11: קבלה כבר נוצלה (double-spend)');
      }
      if (rid) {
        (this._spentReceipts ??= new Map()).set(rid, Date.now());
        if (this._spentReceipts.size > 10_000) {
          let oldestK = null, oldestT = Infinity;
          for (const [k, ts] of this._spentReceipts) { if (ts < oldestT) { oldestT = ts; oldestK = k; } }
          if (oldestK != null) this._spentReceipts.delete(oldestK);
        }
      }
    }
    // S15: policy-as-constraint — ברירת מחדל: active-policy / access אם מוגדר
    let policyName = opts.policy || this._defaultPolicy || null;
    if (!policyName && this.cloud) {
      try {
        const ap = this.cloud._pulse?.()?.scenario?.activePolicy;
        if (ap) policyName = String(ap).replace(/^policy:/, '');
      } catch { /* ignore */ }
    }
    if (policyName || 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 (policyName && this.cloud?.resolveSite) {
        const out = await this.cloud.resolveSite(policyName, { inputs: { ...(opts.policyInputs || {}), __key: key, __ns: ns, __value: value }, 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);
    if (opts._coerced !== undefined) value = opts._coerced;
    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, ns: String(ns), value });
    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, ns: String(ns), value: sealedPayload, sealed: bound.ctxDigest });
    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;
  }

  async del(ns, key, opts = {}) {
    await this._gate(ns, key, null, { ...opts, op: 'del' });
    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, ns: String(ns) });
    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(await 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 };
  }

  /** S135: שיחזור מקטעי-WAL זרים אל ה-adapter החם (eventual apply) */
  replayWalPending(pending, opts = {}) {
    const list = Array.isArray(pending) ? pending : [];
    let applied = 0, skipped = 0;
    for (const rec of list) {
      try {
        if (!rec || !rec.key) { skipped++; continue; }
        const ns = rec.ns || opts.ns || 'wal';
        if (rec.op === 'del') {
          this._adapter.del(String(ns), String(rec.key));
        } else {
          let value = rec.value;
          if (value === undefined && rec.payloadCanon != null) {
            try { value = JSON.parse(rec.payloadCanon); } catch { value = rec.payloadCanon; }
          }
          this._adapter.put(String(ns), String(rec.key), {
            value, digest: rec.digest || null, seq: rec.seq || 0, hlc: rec.hlc || null, sealed: rec.sealed || null,
          });
        }
        if (rec.shard && rec.digest) this._track(rec.shard, rec);
        applied++;
      } catch { skipped++; }
    }
    return { ok: true, applied, skipped, n: list.length };
  }

  pinCheckpoint(seqOrTip, opts = {}) {
    const pin = typeof seqOrTip === 'object' ? { ...seqOrTip } : { seq: seqOrTip | 0 };
    // S13: צילום tip-ים + אופציונלית הקפאת כתיבות
    pin.tipsSnapshot = Object.fromEntries(this._tips);
    pin.ts = Date.now();
    this._pinned = pin;
    if (opts.freeze !== false) this._freezeWrites = true;
    return this._pinned;
  }

  /** קריאה לוגית "בטיפ הנעוץ" — מחזירה tip מטא; ערכי KV משוחזרים רק אם יש snapshot rows */
  getAtPin(ns, key) {
    if (!this._pinned) return { pinned: null, value: this.get(ns, key) };
    if (this._pinned.rows?.[ns + '\0' + key] !== undefined) {
      return { pinned: this._pinned, value: this._pinned.rows[ns + '\0' + key], atPin: true };
    }
    return { pinned: this._pinned, value: this.get(ns, key), atPin: false };
  }

  unfreezeWrites() { this._freezeWrites = false; return this; }

  /** שומר צילום שורות ל-pin (לשחזור אחרי fork) */
  snapshotRowsForPin(rows) {
    if (!this._pinned) this._pinned = { seq: this._tip('s0').seq | 0 };
    this._pinned.rows = rows || {};
    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,
      freezeWrites: !!this._freezeWrites,
      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 || BigInt('0x' + hash) > BigInt('0x' + 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 (BigInt('0x' + hash) <= BigInt('0x' + 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;
    // S160: משקלי נחיל — ממסר כושל יורד; נתיב חלופי נבחר לפי score
    this._swarmWeights = this._swarmWeights || {};
    const metrics = {};
    for (const r of report) metrics[r.relay] = { latency: r.latency, fails: r.phi > 6 ? 1 : 0 };
    const pick = typeof swarmPickPath === 'function'
      ? swarmPickPath(spare, this._swarmWeights, metrics)
      : { pick: null };
    const ordered = pick.pick
      ? [pick.pick, ...shuffle(spare.filter((u) => u !== pick.pick))]
      : shuffle(spare);
    for (const url of ordered.slice(0, 4)) {
      this.active.add(url);
      this._connect(url);
      if (typeof swarmUpdateWeights === 'function') {
        const h = this._h(url);
        this._swarmWeights = swarmUpdateWeights(this._swarmWeights, url, h.score() > 20).weights;
      }
    }
    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)));

/** שער מועדף (אחרי probe) — לא ננעלים על שער שנחסם אצל רבים */
function preferredGatewayHost() {
  try {
    const st = typeof loadState === 'function' ? loadState() : null;
    const g = st?.preferredGateway;
    if (g && NSITE_GATEWAYS.includes(g)) return g;
  } catch {}
  return NSITE_GATEWAYS[0];
}

/** הכתובת הגלובלית הקבועה של הפרויקט — עובדת גם כשכל המכשירים כבויים */
function globalAddress() {
  if (CUSTOM_DOMAIN) return `https://${CUSTOM_DOMAIN}`;
  if (PUBLIC_WEBSITE) return `https://${npub()}.${preferredGatewayHost()}`;
  const naddr = makeNaddr(dSite(), keys.pubkey, KIND_SITE, 'wss://relay.damus.io');
  return 'https://yakihonne.com/article/' + naddr;
}

/** כתובות מראה — אותו אתר מוגש מכמה שערים בלתי-תלויים */
function mirrorAddresses() {
  const pref = preferredGatewayHost();
  const ordered = [pref, ...NSITE_GATEWAYS.filter((g) => g !== pref)];
  return ordered.map((g) => `https://${npub()}.${g}`);
}

/**
 * חבילת גישה נגד חסימות רשת — תמיד לשתף כמה מראות, לא קישור יחיד.
 * אותו תוכן בכל השערים; אם ספק חוסם nosto.re ייתכן ש-nsite.lol עדיין נפתח.
 */
function gatewayAccessPackPlan(opts = {}) {
  const mirrors = (opts.mirrors || mirrorAddresses()).slice();
  const virtual = opts.virtual && opts.virtual.ok !== false ? opts.virtual : null;
  const virtualUrl = virtual?.short || (virtual?.host ? `https://${virtual.host}` : null);
  if (opts.domain) {
    const d = 'https://' + String(opts.domain).replace(/^https?:\/\//, '');
    if (!mirrors.includes(d)) mirrors.unshift(d);
  }
  let primary = opts.primary || null;
  if (!primary) {
    if (virtualUrl) primary = virtualUrl;
    else if (opts.domain) primary = 'https://' + String(opts.domain).replace(/^https?:\/\//, '');
    else primary = mirrors[0] || null;
  }
  const rest = mirrors.filter((u) => u !== primary && u !== virtualUrl);
  const pack = {
    v: 2,
    kind: 'skycloud-access-pack',
    primary,
    virtual: virtualUrl || null,
    virtualHost: virtual?.host || null,
    canon: virtual?.canon || virtual?.canonical || mirrors[0] || primary,
    open: virtual?.open || null,
    npub: virtual?.npub || null,
    mirrors,
    gateways: (opts.gateways || NSITE_GATEWAYS).slice(),
    preferredGateway: opts.preferredGateway || preferredGatewayHost(),
    accessPath: '/access.html',
    accessJson: '/access.json',
    noteHe: virtualUrl
      ? 'הכתובת הקצרה (סיומת המנוע) מפעילה את אותו אתר — אם לא נפתחת בדפדפן, השתמשו בכניסה הפתוחה או במראות npub.'
      : 'אם קישור אחד לא נפתח — נסו מראה אחרת מהרשימה (אותו אתר). אל תניחו שהאתר מת אם שער אחד חסום.',
    noteEn: virtualUrl
      ? 'Virtual short address points to the same site — if the browser cannot resolve it, use open URL or npub mirrors.'
      : 'If one link is blocked on your network, try another mirror — same site. One blocked gateway ≠ site down.',
    shareTextHe: [
      virtualUrl ? `כתובת קצרה (סיומת המנוע): ${virtualUrl}` : null,
      virtual?.open ? `כניסה פעילה: ${virtual.open}` : null,
      'כניסה לאתר (נסו לפי הסדר אם אחד נחסם):',
      primary && primary !== virtualUrl ? '1) ' + primary : (primary ? '1) ' + primary : ''),
      ...rest.map((u, i) => (i + (primary ? 2 : 1)) + ') ' + u),
      'דף מראות: ' + (String(virtual?.canon || mirrors[0] || primary || '').replace(/\/$/, '') + '/access.html'),
    ].filter(Boolean).join('\n'),
    ts: opts.now || Date.now(),
  };
  pack.digest = hex(sha256(Buffer.from('access-pack:v1:' + xiCanon({
    primary: pack.primary, mirrors: pack.mirrors, pref: pack.preferredGateway,
  })))).slice(0, 24);
  return { ok: true, pack, nMirrors: mirrors.length };
}

/** HTML ציבורי — רשימת מראות + בדיקה בדפדפן איזו עובדת אצל המבקר */
function buildAccessHtml(pack) {
  const mirrors = pack.mirrors || [];
  const links = mirrors.map((u, i) =>
    `<li data-url="${u.replace(/"/g, '')}"><a href="${u.replace(/"/g, '')}">${i + 1}. ${u.replace(/&/g, '&amp;')}</a> <span class="st">…</span></li>`
  ).join('\n');
  return `<!DOCTYPE html><html lang="he" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>כניסה / מראות SkyCloud</title>
<style>
body{font-family:system-ui,Segoe UI,Tahoma,sans-serif;background:linear-gradient(160deg,#0f1419,#1a2332 50%,#121820);color:#e8eef6;margin:0;padding:28px 18px;line-height:1.5}
main{max-width:640px;margin:0 auto}
h1{font-size:1.45rem;font-weight:700;margin:0 0 8px}
p{opacity:.9;margin:0 0 16px}
ul{list-style:none;padding:0;margin:0 0 20px}
li{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:12px 14px;margin:0 0 8px}
a{color:#8ec7ff;word-break:break-all}
.st{float:left;font-size:.85rem;opacity:.85}
.ok{color:#6dca8c}.bad{color:#f0a0a0}.warn{color:#e6c07b}
button{background:#2d6cdf;color:#fff;border:0;border-radius:8px;padding:10px 16px;font-size:1rem;cursor:pointer;margin:0 8px 8px 0}
pre{white-space:pre-wrap;background:rgba(0,0,0,.35);padding:12px;border-radius:8px;font-size:.85rem}
</style></head><body><main>
<h1>כניסה לאתר — כמה כתובות (מראות)</h1>
${pack.virtual ? `<p><strong>כתובת קצרה (סיומת המנוע):</strong> <code>${String(pack.virtual).replace(/</g, '&lt;')}</code>${pack.open ? ` · <a href="${String(pack.open).replace(/"/g, '')}">כניסה פעילה</a>` : ''}</p>` : ''}
<p>אם קישור אחד חסום אצלכם ברשת — זה נפוץ. אותו אתר זמין בכל הכתובות למטה. נסו אחת שעובדת.</p>
<p class="warn" id="hint">בודק אילו מראות נגישות מהרשת שלכם…</p>
<ul id="list">${links}</ul>
<p>
<button type="button" id="best">פתח מראה שעובדת</button>
<button type="button" id="copy">העתק את כל הרשימה לשיתוף</button>
</p>
<pre id="share">${(pack.shareTextHe || '').replace(/</g, '&lt;')}</pre>
<script>
(function(){
  var items=[].slice.call(document.querySelectorAll('#list li'));
  var okList=[];
  function check(li){
    var u=li.getAttribute('data-url');
    var st=li.querySelector('.st');
    var ctrl=new AbortController();
    var t=setTimeout(function(){ctrl.abort();},4500);
    return fetch(u.replace(/\\/$/,'')+'/access.json',{cache:'no-store',mode:'cors',signal:ctrl.signal})
      .then(function(r){ clearTimeout(t); if(r.ok){ st.textContent='✓ זמין'; st.className='st ok'; okList.push(u); return true; }
        st.textContent='✗ לא נפתח מכאן'; st.className='st bad'; return false; })
      .catch(function(){ clearTimeout(t); st.textContent='✗ חסום/לא זמין מהרשת הזו'; st.className='st bad'; return false; });
  }
  Promise.all(items.map(check)).then(function(){
    var h=document.getElementById('hint');
    if(okList.length) h.textContent='נמצאו '+okList.length+' מראות זמינות מהרשת שלכם. אפשר לפתוח או לשתף.';
    else h.textContent='לא זוהתה מראה חיה מבדיקה אוטומטית — נסו ללחוץ ידנית על הקישורים (לפעמים CORS חוסם בדיקה אבל הדף עצמו נפתח).';
  });
  document.getElementById('best').onclick=function(){
    var u=okList[0]|| (items[0]&&items[0].getAttribute('data-url'));
    if(u) location.href=u;
  };
  document.getElementById('copy').onclick=function(){
    var t=document.getElementById('share').textContent;
    if(navigator.clipboard&&navigator.clipboard.writeText) navigator.clipboard.writeText(t);
    else { var a=document.createElement('textarea'); a.value=t; document.body.appendChild(a); a.select(); document.execCommand('copy'); document.body.removeChild(a); }
    document.getElementById('hint').textContent='הרשימה הועתקה — שלחו אותה לחברים (לא רק קישור אחד).';
  };
})();
</script>
</main></body></html>`;
}

/** SkyHandle — שם קצר מעל ה-npub (לא מחליף קריפטו; Roam+shell מגשרים) */
function skyHandleOf(slug) {
  const s = slugify(slug || SITE_SLUG) || 'site';
  return { handle: s, sky: 'sky://' + s, atPath: '/@', atUrl: () => globalAddress().replace(/\/$/, '') + '/@' };
}

/** סיומת וירטואלית של המנוע — לא DNS; רק תווים [a-z0-9] ללא נקודה מובילה */
function normalizeSkySuffix(suffix) {
  if (suffix === false || suffix === 0 || suffix == null) return null;
  const raw = String(suffix).trim().toLowerCase().replace(/^\./, '');
  if (!raw || raw === 'off' || raw === 'false' || raw === 'none') return null;
  if (!/^[a-z][a-z0-9]{1,11}$/.test(raw)) return null;
  return raw;
}

function virtualShortHost(slug, suffix) {
  const s = slugify(slug || SITE_SLUG) || 'site';
  const suf = normalizeSkySuffix(suffix) || 'io';
  return `${s}.${suf}`;
}

/**
 * S181: כתובת קצרה וירטואלית — slug.suffix מעל npub.
 * לא דומיין רשום; נשמר ב-Roam (virt:host → site:canon) ומופעל דרך השער הקנוני / mesh.
 */
function virtualGlobalAddressPlan(opts = {}) {
  const enabled = opts.enabled != null ? !!opts.enabled : !!SKY_SUFFIX_ENABLED;
  const suffix = normalizeSkySuffix(opts.suffix != null ? opts.suffix : SKY_SUFFIX);
  if (!enabled || !suffix) return { ok: false, reason: 'disabled', virtual: false };
  const slug = slugify(opts.slug || opts.handle || SITE_SLUG) || 'site';
  const host = virtualShortHost(slug, suffix);
  const canon = opts.canonical || globalAddress();
  const open = opts.open || String(canon).replace(/\/$/, '') + '/@';
  const npubVal = opts.npub != null ? opts.npub : (typeof npub === 'function' ? npub() : null);
  return {
    ok: true,
    v: 1,
    virtual: true,
    kind: 'skycloud-virtual-address',
    slug,
    suffix,
    host,
    short: `https://${host}`,
    shortLabel: host,
    sky: 'sky://' + slug,
    canon,
    canonical: canon,
    npub: npubVal,
    pubkey: opts.pubkey || null,
    open,
    roamVirt: `virt:${host}`,
    roamAlias: `alias:${slug}`,
    noteHe: 'כתובת קצרה של המנוע — לא דומיין רשום; נפתרת ברשת המבוזרת אל הכתובת הקנונית (npub).',
    noteEn: 'Engine virtual suffix — not purchased DNS; resolves via decentralized mesh to canonical npub gateway.',
  };
}

/** פענוח host וירטואלי (my-game.io) */
function parseVirtualHost(input) {
  const s = String(input || '').trim().replace(/^https?:\/\//i, '').replace(/\/.*$/, '').toLowerCase();
  const m = s.match(/^([a-z0-9][a-z0-9_-]{0,63})\.([a-z][a-z0-9]{1,11})$/);
  if (!m) return null;
  const slug = slugify(m[1]);
  const suffix = m[2];
  if (!slug || !suffix) return null;
  return { slug, suffix, host: `${slug}.${suffix}` };
}

/** כתובת וירטואלית לחבילת גישה / UI — מ-state או מחישוב מקומי */
function virtualAddressForPack(st = null) {
  try {
    const state = st || loadState();
    if (state.virtualAddress && state.virtualAddress.ok !== false && state.virtualAddress.short) return state.virtualAddress;
    if (state.addressStack?.short) {
      return {
        ok: true,
        virtual: true,
        short: state.addressStack.short,
        host: state.addressStack.host,
        suffix: state.addressStack.suffix,
        slug: state.addressStack.slug,
        canon: state.addressStack.canonical,
        canonical: state.addressStack.canonical,
        open: state.addressStack.open || state.addressStack.at,
        npub: state.addressStack.npub,
        sky: state.addressStack.sky,
      };
    }
  } catch { /* ignore */ }
  return virtualGlobalAddressPlan({
    slug: SITE_SLUG,
    canonical: globalAddress(),
    npub: typeof npub === 'function' ? npub() : null,
    pubkey: keys?.pubkey || null,
  });
}

/** S19: פרופיל-קפסולה לפי תרחיש */
function capsuleGenomeSelect(scenario, map) {
  const s = typeof scenario === 'string' ? scenario : scenario?.scenario;
  const defaults = {
    logic_fork: 'offline-nabla',
    relay_degraded: 'degraded',
    db_lag: 'degraded',
    high_load: 'degraded',
    cold_start: 'full',
    cache_hot: 'full',
    healthy_steady: 'full',
    default: 'full',
  };
  const m = { ...defaults, ...(map || {}) };
  return m[s] || m.default || 'full';
}

/** S21: חבילת מיגרציה דטרמיניסטית (handle+schema+policy tip) */
function migrationBundle(parts = {}) {
  const body = {
    v: 1,
    handle: parts.handle || null,
    schemaDiff: parts.schemaDiff || null,
    policyTip: parts.policyTip || null,
    ts: parts.ts || Date.now(),
  };
  body.id = hex(sha256(Buffer.from('skymig:v1:' + xiCanon(body)))).slice(0, 32);
  return body;
}

/** S14: DAG טהור בלבד — ערכים + אופרטורי XI_OPS (בלי תופעות-לוואי) */
function dagIsPure(dag) {
  if (!dag?.nodes) return false;
  for (const n of Object.values(dag.nodes)) {
    if (!n || typeof n !== 'object') continue;
    if (n.when || n.branch || n.coalesce) return false;
    if (n.op != null && !XI_OPS[n.op]) return false;
    if (n.op == null && !('value' in n)) return false;
  }
  return true;
}

/**
 * S12: אכיפת סכמה על ערך — schema[field] = { type, required? } או מחרוזת-סוג.
 * מחזיר { ok, reason?, coerced? }.
 */
function skydbValidateSchema(schema, ns, key, value) {
  if (!schema || typeof schema !== 'object' || !Object.keys(schema).length) return { ok: true };
  // סכמה ברמת ns או ברמת מפתח מלא
  const spec = schema[ns + ':' + key] || schema[key] || schema[ns] || null;
  if (!spec) return { ok: true };
  const type = typeof spec === 'string' ? spec : spec.type;
  const required = typeof spec === 'object' && !!spec.required;
  if (value == null || value === undefined) {
    if (required) return { ok: false, reason: 'required' };
    return { ok: true };
  }
  const t = Array.isArray(value) ? 'list' : (value !== null && typeof value === 'object' ? 'map' : typeof value);
  const alias = { array: 'list', object: 'map', number: 'number', string: 'string', boolean: 'boolean', counter: 'number' };
  const want = alias[type] || type;
  const got = alias[t] || t;
  if (want && want !== 'any' && got !== want) {
    // כפייה קלה: מחרוזת מספרית → number
    if (want === 'number' && typeof value === 'string' && value.trim() !== '' && !Number.isNaN(Number(value))) {
      return { ok: true, coerced: Number(value) };
    }
    return { ok: false, reason: `type:${want}!=${got}` };
  }
  return { ok: true };
}

/** S5: קוורום digests מרובי-מחברים (לא אותו מחשב N פעמים) */
function quorumDigests(reports, k = 2) {
  const by = new Map();
  for (const r of reports || []) {
    if (!r?.digest) continue;
    const author = r.author || r.visitor || 'anon';
    if (!by.has(r.digest)) by.set(r.digest, { digest: r.digest, authors: new Set() });
    by.get(r.digest).authors.add(author);
  }
  const ranked = [...by.values()].map((g) => ({ digest: g.digest, agree: g.authors.size, authors: [...g.authors] }))
    .sort((a, b) => b.agree - a.agree);
  if (!ranked.length) return { ok: false, agree: 0, need: k };
  const top = ranked[0];
  return { ok: top.agree >= k, agree: top.agree, need: k, digest: top.digest, authors: top.authors, rivals: ranked.length - 1 };
}

// ═══════════ SkyLattice-Μ: SkyMind + דלתות אוטונומיה/השהייה (S23–S30) ═══════════

/** נרמול הודעות לצ'אט */
function mindNormalizeMessages(input, system) {
  let msgs = [];
  if (typeof input === 'string') msgs = [{ role: 'user', content: input }];
  else if (Array.isArray(input)) msgs = input.map((m) => ({ role: m.role || 'user', content: String(m.content ?? m.text ?? '') }));
  else if (input && typeof input === 'object' && input.content) msgs = [{ role: input.role || 'user', content: String(input.content) }];
  else msgs = [{ role: 'user', content: String(input ?? '') }];
  if (system) msgs = [{ role: 'system', content: String(system) }, ...msgs.filter((m) => m.role !== 'system')];
  return msgs;
}

/** מזהה מטמון דטרמיניסטי — אותו קלט+מודל+הקשר = אותה תשובה מה־DB */
function mindPromptDigest(messages, model, ctx = {}) {
  return hex(sha256(Buffer.from('skymind:v1:' + xiCanon({ messages, model: model || '', ctx: ctx || {} })))).slice(0, 40);
}

/**
 * S26 Delta: שולחים לספק רק הודעות חדשות מאז tip שיחה + תקציר קצר של ההיסטוריה.
 * חוסך tokens בכל סיבוב ארוך.
 */
function mindDeltaMessages(prevMessages, nextMessages, opts = {}) {
  const prev = Array.isArray(prevMessages) ? prevMessages : [];
  const next = Array.isArray(nextMessages) ? nextMessages : [];
  if (!prev.length) return { messages: next, delta: false, saved: 0 };
  let common = 0;
  while (common < prev.length && common < next.length && xiCanon(prev[common]) === xiCanon(next[common])) common++;
  const fresh = next.slice(common);
  if (!fresh.length) return { messages: next.slice(-1), delta: true, saved: Math.max(0, next.length - 1) };
  const maxPrev = opts.summaryChars || 800;
  const older = prev.slice(0, common).map((m) => (m.role || '?') + ': ' + String(m.content || '').slice(0, 120)).join('\n');
  const summary = older.length > maxPrev ? older.slice(-maxPrev) : older;
  const out = [];
  if (summary) out.push({ role: 'system', content: '[SkyMind Δ prior context]\n' + summary });
  for (const m of fresh) out.push(m);
  const dropped = next.slice(0, common);
  const approxSaved = Math.max(0, JSON.stringify(dropped).length);
  return { messages: out, delta: true, saved: approxSaved, common };
}

/** ∇ מקומי: דחיסת פרומפט בלי LLM — חיתוך + הסרת כפילויות */
function mindCompactPrompt(messages, opts = {}) {
  const maxChars = opts.maxChars || 6000;
  const maxMsg = opts.maxMsg || 24;
  let msgs = mindNormalizeMessages(messages, null);
  // משאירים system ראשון + N אחרונים
  const sys = msgs.filter((m) => m.role === 'system');
  let rest = msgs.filter((m) => m.role !== 'system');
  if (rest.length > maxMsg) rest = rest.slice(-maxMsg);
  // הסרת הודעות user זהות ברצף
  const dedup = [];
  for (const m of rest) {
    const prev = dedup[dedup.length - 1];
    if (prev && prev.role === m.role && prev.content === m.content) continue;
    dedup.push({ ...m, content: String(m.content).slice(0, opts.perMsgChars || 4000) });
  }
  let out = [...sys.slice(0, 1), ...dedup];
  let packed = JSON.stringify(out);
  while (packed.length > maxChars && out.length > 2) {
    // מוחקים מהאמצע (אחרי system)
    out.splice(1, 1);
    packed = JSON.stringify(out);
  }
  return { messages: out, bytes: packed.length, compacted: packed.length < JSON.stringify(msgs).length };
}

/** הערכת עלות גסה ל-governor */
function mindEstimateCost(tokensIn, tokensOut, cfg = {}) {
  const tin = (tokensIn || 0) / 1000 * (cfg.usdPer1kIn ?? MIND.usdPer1kIn);
  const tout = (tokensOut || 0) / 1000 * (cfg.usdPer1kOut ?? MIND.usdPer1kOut);
  return +(tin + tout).toFixed(6);
}

/** S25: מושל עלויות/מצב — מחליט cache-only / cheap / full / deny */
function mindGovernor(stats, cfg = {}, genome = 'full') {
  const day = stats?.day || '';
  const spent = stats?.usd || 0;
  const budget = cfg.budgetUsdPerDay ?? MIND.budgetUsdPerDay;
  const lag = stats?.latencyEwmaMs || 0;
  if (genome === 'offline-nabla') return { mode: 'cache-only', reason: 'genome-offline', model: null };
  if (spent >= budget) return { mode: 'cache-only', reason: 'budget', model: null };
  if (spent >= budget * 0.85 || lag > 4000 || genome === 'degraded') {
    return { mode: 'cheap', reason: spent >= budget * 0.85 ? 'budget-high' : (lag > 4000 ? 'latency' : 'genome'), model: cfg.cheapModel || MIND.cheapModel };
  }
  return { mode: 'full', reason: 'ok', model: cfg.model || MIND.model };
}

/** S24: מירוץ השהייה — הראשון שמצליח מנצח */
async function latencyRace(producers, opts = {}) {
  const list = (producers || []).filter((p) => typeof p === 'function' || (p && typeof p.fn === 'function'));
  if (!list.length) throw new Error('latencyRace: אין יצרנים');
  const timeoutMs = opts.timeoutMs || 8000;
  const start = Date.now();
  return new Promise((resolve, reject) => {
    let done = false, pending = list.length, lastErr = null;
    const timer = setTimeout(() => {
      if (!done) { done = true; reject(lastErr || new Error('latencyRace: timeout')); }
    }, timeoutMs);
    for (const p of list) {
      const name = (typeof p === 'function' ? 'p' : (p.name || 'p'));
      const fn = typeof p === 'function' ? p : p.fn;
      Promise.resolve()
        .then(() => fn())
        .then((value) => {
          if (done) return;
          if (value === null || value === undefined) {
            pending--;
            if (!pending) { done = true; clearTimeout(timer); reject(lastErr || new Error('latencyRace: empty')); }
            return;
          }
          done = true; clearTimeout(timer);
          resolve({ value, winner: name, ms: Date.now() - start });
        })
        .catch((err) => {
          lastErr = err; pending--;
          if (!pending && !done) { done = true; clearTimeout(timer); reject(lastErr); }
        });
    }
  });
}

/** S29: צל-∇ על תשובת LLM — מדיניות מאשרת/עורכת לפני החזרה ללקוח */
function mindShadowGate(output, policyOutput) {
  if (policyOutput === 0 || policyOutput === false || policyOutput === 'deny') {
    return { ok: false, output: null, reason: 'policy-deny' };
  }
  if (policyOutput && typeof policyOutput === 'object' && policyOutput.rewrite != null) {
    return { ok: true, output: policyOutput.rewrite, reason: 'rewrite' };
  }
  if (typeof policyOutput === 'string' && policyOutput.startsWith('rewrite:')) {
    return { ok: true, output: policyOutput.slice(8), reason: 'rewrite' };
  }
  return { ok: true, output, reason: 'pass' };
}

/** S27: סיכום טלמטריה לאוטופיילוט */
function telemetryAutopilotPlan(pulse, mindStats, dbHealth) {
  const actions = [];
  const sc = pulse?.scenario?.scenario;
  if (sc === 'db_lag' || (dbHealth?.lagMs || 0) > 30_000) actions.push({ op: 'checkpoint', priority: 2 });
  if (sc === 'relay_degraded' || sc === 'high_load') actions.push({ op: 'prefer_cache', priority: 3 });
  if (sc === 'logic_fork') actions.push({ op: 'freeze', priority: 5 });
  if ((mindStats?.usd || 0) > (MIND.budgetUsdPerDay * 0.9)) actions.push({ op: 'mind_cache_only', priority: 4 });
  if ((pulse?.vitality || 1) < 0.45) actions.push({ op: 'heal', priority: 3 });
  if ((mindStats?.hitRate || 1) < 0.2 && (mindStats?.calls || 0) > 10) actions.push({ op: 'warmup_mind', priority: 1 });
  actions.sort((a, b) => b.priority - a.priority);
  return { actions, top: actions[0]?.op || 'steady', ts: Date.now() };
}

// ═══════════ Apparent Physics Lattice S31–S36 — עקיפת מגבלות מוצר ═══════════

/** S36: ציון SLA מלב עובדים חיים (לא הבטחת AWS — מדידה פנימית) */
function slaScoreFromWorkers(workers, now = Date.now(), ttlMs = WORKER_TTL_MS) {
  const list = Object.values(workers || {}).filter((w) => w && (now - (w.ts || 0)) < ttlMs);
  const n = list.length;
  const avgOps = n ? list.reduce((s, w) => s + (w.opsPerSec || 0), 0) / n : 0;
  const freshness = n ? list.reduce((s, w) => s + Math.max(0, 1 - (now - w.ts) / ttlMs), 0) / n : 0;
  const score = Math.min(99.999, +(50 * Math.min(1, n / 3) + 30 * freshness + 20 * Math.min(1, avgOps / 1000)).toFixed(3));
  return {
    workersAlive: n, score, freshness: +freshness.toFixed(3), avgOpsPerSec: +avgOps.toFixed(1),
    target: n >= 2 ? 'ha' : (n === 1 ? 'single' : 'cold'),
    peers: list.map((w) => ({ id: w.id, opsPerSec: w.opsPerSec, lagMs: w.lagMs })),
  };
}

/** S32: peers חיים מ־registry */
function meshPeersFromRegistry(registry, now = Date.now(), ttlMs = WORKER_TTL_MS) {
  return Object.values(registry || {})
    .filter((w) => w && w.id && (now - (w.ts || 0)) < ttlMs)
    .map((w) => w.id);
}

/** S32: תפוקת כתיבה *נראית* — סכום shards על עובדים (לא ממסרים) */
function apparentWriteThroughput(localOpsPerSec, nOwners) {
  const n = Math.max(1, nOwners | 0);
  return { localOpsPerSec: localOpsPerSec || 0, owners: n, apparentOpsPerSec: (localOpsPerSec || 0) * n, note: 'hot-path×owners — not relay ingest' };
}

/** S34: רשימת מראות CDN (nsite + domain + blossom) */
function cdnMirrorList(opts = {}) {
  const pub = opts.npub || '';
  const gateways = opts.gateways || NSITE_GATEWAYS;
  const mirrors = gateways.map((g) => ({ kind: 'nsite', url: `https://${pub}.${g}` }));
  if (opts.domain) mirrors.unshift({ kind: 'domain', url: `https://${String(opts.domain).replace(/^https?:\/\//, '')}` });
  if (opts.handleAt) mirrors.push({ kind: 'handle', url: opts.handleAt });
  if (opts.callingCard) mirrors.push({ kind: 'calling-card', url: opts.callingCard });
  for (const b of (opts.blossom || BLOSSOM_SERVERS).slice(0, 4)) mirrors.push({ kind: 'blossom', url: b });
  return mirrors;
}

/** S33: תוכנית כתובת כפולה / משולשת + S181 כתובת וירטואלית קצרה */
function addressStackPlan(opts = {}) {
  const virtual = opts.virtual && opts.virtual.ok !== false ? opts.virtual : null;
  return {
    canonical: opts.canonical || virtual?.canon || virtual?.canonical || null,
    short: virtual?.short || opts.short || null,
    virtual: !!(virtual?.virtual || virtual?.short || opts.virtual === true),
    host: virtual?.host || opts.host || null,
    suffix: virtual?.suffix || opts.suffix || normalizeSkySuffix(SKY_SUFFIX),
    slug: virtual?.slug || opts.slug || null,
    open: virtual?.open || opts.open || opts.at || null,
    npub: virtual?.npub || opts.npub || null,
    sky: opts.sky || virtual?.sky || null,
    at: opts.at || virtual?.open || null,
    domain: opts.domain || null,
    callingCard: opts.callingCard || null,
    mirrors: (opts.mirrors || []).slice(0, 8),
    note: 'npub stays long; short virtual suffix + Roam/domain/card bridges',
  };
}

/**
 * Living Global Address — תוכנית מוצר: כתובת = כניסה חיה לרשת (לא CDN סטטי בלבד).
 * אותה מודל ל-nsite ול-CUSTOM_DOMAIN. השער מגיש מעטפת; החיים ב-mesh/∇/snapshots.
 */
function livingAddressPlan(opts = {}) {
  const apiPrefix = opts.apiPrefix || API_PREFIX || '/api';
  return {
    v: 1,
    living: true,
    mode: 'living-mesh',
    staticOnly: false,
    trustServer: false,
    apiPrefix,
    mesh: true,
    visitorsAreWorkers: true,
    snapshots: true,
    nabla: true,
    sync: true,
    users: true,
    domainParity: true,
    canonical: opts.canonical || null,
    domain: opts.domain || null,
    sky: opts.sky || null,
    at: opts.at || null,
    callingCard: opts.callingCard || null,
    snapshotPaths: opts.snapshots || opts.snapshotPaths || null,
    workersAlive: opts.workersAlive ?? null,
    layers: ['shell', 'cloud-web', 'mesh-api', 'snapshots', 'nabla', 'webkv', 'users'],
    note: 'nsite serves shell+SDK; Node-equivalent via Ω mesh + visitor workers + API snapshots + ∇; same path as CUSTOM_DOMAIN — not gateway Node',
  };
}

/** מונע התנגשות שמייצרת "רק סטטי" — חייב SDK + living markers + apiPrefix + trustServer=false */
function livingAddressAntiStaticGate(meta = {}, paths = []) {
  const pathList = (paths || []).map((p) => (Array.isArray(p) ? p[0] : p));
  const hasSdk = pathList.includes('/cloud-web.js') || !!meta.hasCloudWeb;
  const hasSkylive = pathList.includes('/skylive.json') || !!meta.hasSkylive || meta.living === true;
  const living = !!(meta.living || meta.mode === 'living-mesh');
  const reasons = [];
  if (!hasSdk) reasons.push('missing-cloud-web');
  if (!living) reasons.push('skylive-not-living');
  if (!meta.apiPrefix) reasons.push('missing-apiPrefix');
  if (meta.staticOnly === true) reasons.push('staticOnly-flag');
  if (meta.trustServer === true) reasons.push('trustServer-forbidden');
  if (!hasSkylive && living) reasons.push('missing-skylive');
  return {
    ok: reasons.length === 0,
    living: living && meta.staticOnly !== true,
    staticCollision: reasons.includes('staticOnly-flag') || (!living && !meta.mesh),
    reasons,
  };
}

/** S31: חבילת אמון מבקר — מה חייב להיות מאומת אצל הלקוח */
function zeroTrustBundle(out = {}) {
  return {
    v: 1,
    trustServer: false,
    require: ['client-eval', 'receipt', 'context', 'pathDigest'],
    verified: !!(out.verified && (out.verified.match === true || out.verified === true)),
    receiptId: out.receipt?.id || null,
    pathDigest: out.trace?.pathDigest || null,
    contextOk: out.contextOk !== false,
    source: out.source || null,
  };
}

/** S38: חבילת אפס-אמון לתשובת Mind */
function mindZeroTrustPackage(askOut = {}) {
  const receipt = askOut.receipt || null;
  return {
    trustServer: false,
    ok: !!(askOut.ok && askOut.content != null),
    digest: askOut.digest || null,
    source: askOut.source || null,
    receiptId: receipt?.id || null,
    costUsd: askOut.costUsd || 0,
    governor: askOut.governor?.mode || null,
    verify: ['receipt', 'digest', 'cache-replay'],
    note: 'Visitor may re-ask with same digest — cache hit proves replay without trusting origin',
  };
}

/** S40: אימות קפסולה — digest על ה-embed */
function capsuleAttest(embed) {
  if (!embed?.b64) return { ok: false, reason: 'no-embed' };
  const digest = hex(sha256(Buffer.from('skycap:v1:' + embed.b64 + ':' + (embed.nFiles || 0) + ':' + (embed.packedAt || 0)))).slice(0, 40);
  return { ok: true, digest, nFiles: embed.nFiles || 0, packedAt: embed.packedAt || null, outDir: embed.outDir || '.sky-unpacked' };
}

/** S39: בחירת עובד Mind לפי SLA (lag נמוך / ops גבוה / זול) */
function pickMeshMindWorker(workers, opts = {}) {
  const now = opts.now || Date.now();
  const ttl = opts.ttlMs || WORKER_TTL_MS;
  const alive = Object.values(workers || {}).filter((w) => w && w.id && (now - (w.ts || 0)) < ttl && w.node !== false);
  if (!alive.length) return { worker: null, reason: 'no-workers' };
  const prefer = opts.prefer || 'lag'; // lag | ops | local
  let best = alive[0];
  for (const w of alive) {
    if (prefer === 'ops') {
      if ((w.opsPerSec || 0) > (best.opsPerSec || 0)) best = w;
    } else if (prefer === 'local' && opts.localId && w.id === opts.localId) {
      best = w; break;
    } else {
      // lag: lower lagMs wins; missing lag treated as high
      const wl = w.lagMs != null ? w.lagMs : 99999;
      const bl = best.lagMs != null ? best.lagMs : 99999;
      if (wl < bl) best = w;
    }
  }
  return { worker: best, reason: prefer, n: alive.length };
}

/** S42: קוורום אמון — ≥k עובדים מסכימים על digest */
function trustMeshQuorum(attestations, k = 2) {
  return quorumDigests((attestations || []).map((a) => ({ digest: a.digest, author: a.worker || a.author })), k);
}

/** S50: תמחור Mind לפי השהיית mesh (זול יותר כש-lag נמוך / cache) */
function latencyMindPrice(opts = {}) {
  const lag = Math.max(0, opts.lagMs || 0);
  const base = opts.baseUsd ?? 0.0004;
  const source = opts.source || 'provider';
  if (source === 'cache' || source === 'distill' || source === 'zero-origin') {
    return { usd: 0, model: opts.cheapModel || opts.model || 'cache', tier: 'free-cache', lagMs: lag };
  }
  // lag גבוה → מודל זול יותר (חוסכים כסף תחת עומס)
  let mult = 1;
  let model = opts.model || 'full';
  let tier = 'full';
  if (lag > 3000) { mult = 0.35; model = opts.cheapModel || 'cheap'; tier = 'degraded-lag'; }
  else if (lag > 1200) { mult = 0.6; model = opts.cheapModel || model; tier = 'warm-lag'; }
  return { usd: +(base * mult).toFixed(6), model, tier, lagMs: lag, mult };
}

/** S44: שלבי canary לפריסת קפסולה */
function capsuleCanaryPlan(workers, opts = {}) {
  const now = opts.now || Date.now();
  const alive = Object.values(workers || {}).filter((w) => w && w.id && (now - (w.ts || 0)) < (opts.ttlMs || WORKER_TTL_MS));
  const sorted = alive.slice().sort((a, b) => (a.lagMs || 0) - (b.lagMs || 0));
  const canary = sorted[0] || null;
  const rest = sorted.slice(1);
  return {
    stage: canary ? (opts.promote ? 'full' : 'canary') : 'solo',
    canary: canary ? { id: canary.id, lagMs: canary.lagMs } : null,
    full: opts.promote ? sorted.map((w) => w.id) : (canary ? [canary.id] : []),
    pending: opts.promote ? [] : rest.map((w) => w.id),
    nAlive: alive.length,
  };
}

/** S44/S48: האם העובד הזה רשאי למשוך tip לפי שלב canary */
function canaryHealGate(meta, localId, opts = {}) {
  if (!meta || meta.stage === 'full' || meta.stage === 'solo') {
    return { pull: 'mesh', reason: meta?.stage || 'no-canary' };
  }
  if (meta.stage === 'rolled-back') return { pull: 'none', reason: 'rolled-back' };
  if (meta.stage !== 'canary' && meta.stage !== 'canary-sharded') return { pull: 'mesh', reason: 'unknown-stage' };
  // S98: כמה canaries לפי shard
  if (Array.isArray(meta.canaries) && meta.canaries.includes(localId)) {
    return { pull: 'canary', reason: 'am-shard-canary' };
  }
  const canaryId = meta.canary?.id;
  if (!canaryId || canaryId === localId) return { pull: 'canary', reason: 'am-canary' };
  return { pull: 'none', reason: 'await-promote', pending: true };
}

/** S52: האם canary נכשל לפי SLA / unpack */
function canaryRollbackPlan(meta, sla, pullResult, opts = {}) {
  const minScore = opts.minSla ?? 35;
  const reasons = [];
  if (pullResult && pullResult.ok === false) reasons.push('pull:' + (pullResult.reason || 'fail'));
  if (sla && typeof sla.score === 'number' && sla.score < minScore) reasons.push('sla:' + sla.score);
  if (meta?.forceRollback) reasons.push('forced');
  return {
    shouldRollback: reasons.length > 0 && meta?.stage === 'canary',
    reasons,
    restoreDigest: meta?.prevDigest || null,
    genome: opts.genome || 'degraded',
  };
}

/** S51: חבילת CDN ל-distill מאומת + seed */
function distillCdnPlan(opts = {}) {
  const mirrors = (opts.mirrors || []).slice();
  if (opts.seedUrl) mirrors.unshift({ kind: 'visitor-seed', url: opts.seedUrl });
  if (opts.distillUrl) mirrors.unshift({ kind: 'distill', url: opts.distillUrl });
  return {
    v: 1,
    digest: opts.digest || null,
    dagId: opts.dagId || null,
    agree: opts.agree || 0,
    seedUrl: opts.seedUrl || null,
    mirrors,
    trustServer: false,
    note: 'Visitor verifies ∇/receipt — CDN only distributes bytes',
  };
}

/** S53/S94: סליקת קבלות — קיבוץ לפי עובד/פרויקט (+ קשירת federation tip) */
function settlementClearPlan(entries, opts = {}) {
  const byWorker = {};
  const byProject = {};
  let usd = 0;
  for (const e of entries || []) {
    const w = e.worker || 'unknown';
    const p = e.project || e.foreignPub || 'local';
    const amt = +(e.usd || e.price?.usd || 0);
    usd += amt;
    byWorker[w] = +(byWorker[w] || 0) + amt;
    byProject[p] = +(byProject[p] || 0) + amt;
  }
  const federationDigest = opts.federationDigest || null;
  const digest = hex(sha256(Buffer.from('settle:v1:' + xiCanon({
    byWorker, byProject, n: (entries || []).length, usd: +usd.toFixed(6), federationDigest,
  })))).slice(0, 40);
  return {
    v: 1, digest, n: (entries || []).length, usd: +usd.toFixed(6),
    byWorker, byProject, federationDigest, clearedAt: opts.now || Date.now(),
  };
}

/** S94: settlement חייב לכלול את digest של federation:tip */
function fedSettlementBindGate(plan, federationTip) {
  if (!federationTip?.digest) return { ok: false, reason: 'fed-clear-missing-tip' };
  if (!plan?.federationDigest || plan.federationDigest !== federationTip.digest) {
    return { ok: false, reason: 'fed-clear-digest-mismatch', expected: federationTip.digest, got: plan?.federationDigest || null };
  }
  return { ok: true };
}

/** S86: אין distill-CDN בזמן dual-root */
function dualRootDistillGate(trustState) {
  if (trustState?.phase === 'dual-root') return { ok: false, reason: 'dual-root-distill-freeze' };
  return { ok: true, phase: trustState?.phase || null };
}

/** S96: תור continuum מוגבל לפי governor */
function continuumBudgetGate(stats, cfg, genome, queueLen, opts = {}) {
  const g = mindGovernor(stats || {}, cfg || {}, genome || 'full');
  const maxQ = opts.maxQueue ?? (g.mode === 'cache-only' ? 3 : (g.mode === 'cheap' ? 12 : 50));
  if ((queueLen || 0) >= maxQ) return { ok: false, reason: 'continuum-budget', mode: g.mode, maxQ, queueLen: queueLen || 0 };
  return { ok: true, mode: g.mode, maxQ, queueLen: queueLen || 0 };
}

/** S92: warm רק אם seed מאומת (verifyLive markers) */
function warmSeedVerifyGate(seedMeta, opts = {}) {
  if (opts.skipVerifyLive) return { ok: true, reason: 'skipped' };
  if (!seedMeta?.url) return { ok: false, reason: 'warm-seed-missing' };
  if (!seedMeta.digest && seedMeta.verified !== true) return { ok: false, reason: 'warm-seed-unverified' };
  const min = opts.minAgree ?? 1;
  if (seedMeta.agree != null && seedMeta.agree < min) return { ok: false, reason: 'warm-seed-unverified', agree: seedMeta.agree };
  return { ok: true, digest: seedMeta.digest || null };
}

/** S98: canary לפי בעלי shard (לא רק lag) */
function multiCanaryShardPlan(workers, opts = {}) {
  const now = opts.now || Date.now();
  const ttl = opts.ttlMs || WORKER_TTL_MS;
  const alive = Object.values(workers || {}).filter((w) => w && w.id && (now - (w.ts || 0)) < ttl);
  const peers = alive.map((w) => w.id);
  const nShards = Math.max(1, opts.nShards || SKYDB_SHARDS || 8);
  const byShard = [];
  for (let i = 0; i < nShards; i++) {
    const sh = skydbShardOf('canary:shard:' + i, peers, nShards);
    const ownerW = alive.find((w) => w.id === sh.owner) || null;
    byShard.push({
      shard: sh.shard, owner: sh.owner, lagMs: ownerW?.lagMs ?? null, idx: sh.idx,
    });
  }
  const canaries = [...new Set(byShard.map((b) => b.owner).filter(Boolean))];
  return {
    stage: canaries.length ? 'canary-sharded' : 'solo',
    byShard, canaries,
    canary: canaries[0] ? { id: canaries[0] } : null,
    nAlive: alive.length, nShards,
  };
}

/** S97: seed ב-offline-nabla רק עם tip מאומת (לא allowUnverified) */
function genomeLockedSeedGate(genome, verTip, opts = {}) {
  if (genome !== 'offline-nabla') return { ok: true, reason: 'not-offline' };
  const v = verifiedSeedGate(verTip, { ...opts, allowUnverified: false });
  if (!v.ok) return { ok: false, reason: 'genome-locked-seed', inner: v };
  return { ok: true, digest: v.digest };
}

/** S99: תג בריאות לכתובת חיה — workersAlive/score ב-skylive */
function liveAddressHealthBadge(sla, opts = {}) {
  const workersAlive = sla?.workersAlive ?? opts.workersAlive ?? 0;
  const score = typeof sla?.score === 'number' ? sla.score : (opts.score ?? 0);
  const target = sla?.target || (workersAlive >= 2 ? 'ha' : (workersAlive === 1 ? 'single' : 'zero'));
  return {
    v: 1, workersAlive, score, target, living: true, staticOnly: false,
    badge: workersAlive >= 2 ? 'green' : (workersAlive === 1 ? 'amber' : 'red'),
    note: 'skylive health from S36 sla — not gateway Node',
  };
}

/** S100: snapshot ישן מדי → force live mesh (בלי fallback מוקדם) */
function snapshotFreshnessGate(snap, opts = {}) {
  const maxAge = opts.maxAgeMs ?? 120_000;
  const now = opts.now || Date.now();
  if (!snap) return { ok: false, fresh: false, reason: 'no-snap', forceLiveMesh: true, maxAgeMs: maxAge };
  const ts = snap.ts || snap.snapTs || 0;
  const age = ts ? Math.max(0, now - ts) : Infinity;
  if (age > maxAge) {
    return { ok: false, fresh: false, reason: 'snapshot-stale', ageMs: age, maxAgeMs: maxAge, forceLiveMesh: true };
  }
  return { ok: true, fresh: true, ageMs: age, maxAgeMs: maxAge, forceLiveMesh: false };
}

/** S101: promote רק אם כל shard-canaries אישרו את ה-digest */
function shardCanaryPromoteQuorum(meta, attestations, opts = {}) {
  const canaries = (meta?.canaries || (meta?.canary?.id ? [meta.canary.id] : [])).filter(Boolean);
  if (!canaries.length) return { ok: true, reason: 'no-shard-canaries', agree: 0, need: 0, missing: [] };
  if (meta?.stage !== 'canary-sharded' && !opts.requireAlways) {
    return { ok: true, reason: 'not-sharded', canaries, missing: [], agree: 0, need: 0 };
  }
  const digest = opts.digest || meta?.digest || null;
  const byWorker = {};
  for (const a of attestations || []) {
    if (digest && a.digest && a.digest !== digest) continue;
    const w = a.worker || a.author || a.id;
    if (w) byWorker[w] = a;
  }
  const missing = canaries.filter((c) => !byWorker[c]);
  const agree = canaries.length - missing.length;
  return {
    ok: missing.length === 0,
    reason: missing.length ? 'shard-canary-attest-incomplete' : 'shard-canary-quorum',
    canaries, missing, agree, need: canaries.length, digest,
  };
}

/** S102: אחרי drain continuum — שחרור escrow רק כשכל התשלומים עברו */
function continuumEscrowReleasePlan(drainResults, opts = {}) {
  const results = drainResults || [];
  const nPaid = results.filter((r) => r.pay?.ok || (r.priced?.ok && opts.countPricedAsPaid)).length;
  const nUnpaid = results.filter((r) => r.requeued || r.pay?.ok === false).length;
  const shouldSettle = nPaid > 0 && (nUnpaid === 0 || !!opts.settlePartial) && opts.autoSettle !== false;
  return {
    v: 1, shouldSettle, nPaid, nUnpaid, n: results.length,
    reason: shouldSettle ? 'continuum-escrow-release' : (nPaid ? 'continuum-unpaid-hold' : 'continuum-nothing-to-settle'),
  };
}

/** S103: beat של טאב מבקר כעובד mesh */
function visitorWorkerBeatPlan(opts = {}) {
  const raw = String(opts.id || opts.visitorId || 'visitor').slice(0, 24);
  return {
    id: raw.startsWith('v:') ? raw : ('v:' + raw),
    visitor: true, node: false, tab: true,
    opsPerSec: opts.opsPerSec || 0,
    lagMs: opts.lagMs || 0,
    ts: opts.now || Date.now(),
  };
}

/** S104: domain down → סדר מראות nsite ראשון */
function domainMeshFailoverPlan(probeResults, mirrors, opts = {}) {
  const results = probeResults || [];
  const list = (mirrors || []).slice();
  const domainMirrors = list.filter((m) => m.kind === 'domain');
  const nsiteMirrors = list.filter((m) => m.kind === 'nsite');
  const domainUrls = new Set(domainMirrors.map((m) => m.url));
  const domainProbes = results.filter((r) => domainUrls.has(r.url));
  const domainDown = domainMirrors.length > 0 && (domainProbes.length === 0 || domainProbes.every((r) => !r.ok));
  const bestNsite = results.find((r) => r.ok && nsiteMirrors.some((m) => m.url === r.url)) || nsiteMirrors[0] || null;
  const rest = list.filter((m) => m.kind !== 'nsite' && m.kind !== 'domain');
  return {
    failover: domainDown,
    reason: domainDown ? 'domain-down-nsite-failover' : 'domain-ok-or-absent',
    primary: domainDown ? (bestNsite && (bestNsite.url || bestNsite)) : (domainMirrors[0]?.url || null),
    order: domainDown ? [...nsiteMirrors, ...domainMirrors, ...rest] : list,
  };
}

/** S105: מדיניות חיה ב-skylive רק תחת active trust roots */
function trustRootSkyliveGate(skylive, activeRoots, opts = {}) {
  const roots = (activeRoots || []).filter(Boolean);
  if (!roots.length) return { ok: true, reason: 'no-roots-configured' };
  if (opts.allowUnpinnedSkylive) return { ok: true, reason: 'allowed' };
  const tipRoot = skylive?.trustRoot || skylive?.root || null;
  if (tipRoot && roots.includes(tipRoot)) return { ok: true, reason: 'skylive-root-active', tipRoot };
  if (skylive?.living || skylive?.live) {
    if (!tipRoot) return { ok: false, reason: 'skylive-trust-root-missing', activeRoots: roots };
    return { ok: false, reason: 'skylive-trust-root-pin', tipRoot, activeRoots: roots };
  }
  return { ok: true, reason: 'not-living' };
}

/** S106: prefetch לפי governor / תקציב */
function budgetPrefetchGate(stats, cfg, genome, pendingN, opts = {}) {
  const g = mindGovernor(stats || {}, cfg || {}, genome || 'full');
  const maxN = opts.maxN ?? (g.mode === 'cache-only' ? 0 : (g.mode === 'cheap' ? 2 : 8));
  const n = pendingN || 0;
  if (maxN <= 0 || n >= maxN) {
    return { ok: false, reason: 'prefetch-budget', mode: g.mode, maxN, pendingN: n, allow: 0 };
  }
  return { ok: true, mode: g.mode, maxN, pendingN: n, allow: maxN - n };
}

/** S107: tip זר חייב sky+canon (של A כש-requireMatch) */
function federatedAddressStackGate(tip, localStack, opts = {}) {
  if (opts.allowUnaddressed) return { ok: true, reason: 'skipped' };
  const sky = tip?.sky || (tip?.handle ? ('sky://' + String(tip.handle).replace(/^sky:\/\//, '')) : null) || tip?.skyHandle || null;
  const canon = tip?.canon || tip?.canonical || null;
  if (!sky || !canon) return { ok: false, reason: 'fed-address-stack-incomplete', sky, canon };
  const localSky = localStack?.sky || opts.localSky || null;
  const localCanon = localStack?.canonical || localStack?.canon || opts.localCanon || null;
  if (opts.requireMatch) {
    const norm = (s) => String(s || '').replace(/^sky:\/\//, '');
    if (localSky && norm(sky) !== norm(localSky)) {
      return { ok: false, reason: 'fed-sky-mismatch', sky, localSky };
    }
    if (localCanon && canon !== localCanon) {
      return { ok: false, reason: 'fed-canon-mismatch', canon, localCanon };
    }
  }
  return { ok: true, sky, canon, localSky, localCanon };
}

/** S108: canary shards ב-read-only עד promote */
function canarySqlReadOnlyGate(meta, localId, opts = {}) {
  if (!meta || meta.stage === 'full' || meta.stage === 'solo' || meta.stage === 'rolled-back') {
    return { readOnly: false, reason: meta?.stage || 'no-canary' };
  }
  if (meta.stage !== 'canary' && meta.stage !== 'canary-sharded') {
    return { readOnly: false, reason: 'unknown-stage' };
  }
  if (opts.forceWrite) return { readOnly: false, reason: 'forced' };
  const isCanary = (Array.isArray(meta.canaries) && meta.canaries.includes(localId))
    || meta.canary?.id === localId;
  return { readOnly: true, reason: isCanary ? 'canary-sql-readonly' : 'await-promote-readonly', amCanary: !!isCanary };
}

/** S109: seed מעדכן calling-card רק אם verified */
function seedCallingCardSyncGate(seedBody, opts = {}) {
  if (!seedBody?.url) return { ok: false, sync: false, reason: 'seed-missing' };
  if (opts.forceCard) return { ok: true, sync: true, reason: 'forced', url: seedBody.url };
  if (seedBody.verified !== true && !seedBody.digest) {
    return { ok: false, sync: false, reason: 'seed-unverified-card' };
  }
  const min = opts.minAgree ?? 1;
  if (seedBody.agree != null && seedBody.agree < min) {
    return { ok: false, sync: false, reason: 'seed-unverified-card', agree: seedBody.agree, min };
  }
  return { ok: true, sync: true, url: seedBody.url, digest: seedBody.digest || null };
}

/** S110: מס lag על quote לפי EWMA (S50+S3) */
function lagTaxQuotePlan(price, lagEwmaMs, opts = {}) {
  const lag = Math.max(0, lagEwmaMs || 0);
  const base = typeof price === 'number' ? price : +(price?.usd ?? price?.price ?? 0);
  let mult = 1;
  let tier = 'full';
  if (lag > 3000) { mult = 1.45; tier = 'lag-tax-high'; }
  else if (lag > 1200) { mult = 1.2; tier = 'lag-tax-warm'; }
  else if (lag > 400) { mult = 1.08; tier = 'lag-tax-light'; }
  const taxed = +(base * mult).toFixed(6);
  const priced = (typeof price === 'object' && price)
    ? { ...price, usd: taxed, lagTax: { mult, tier, lagEwmaMs: lag } }
    : taxed;
  return { v: 1, baseUsd: base, usd: taxed, mult, tier, lagEwmaMs: lag, price: priced };
}

/** S111: רענון skylive כש-workersAlive/badge משתנה */
function healthBadgeRepublishGate(prevBadge, nextBadge, opts = {}) {
  if (!nextBadge) return { republish: false, reason: 'no-next' };
  if (opts.force) return { republish: true, reason: 'forced', next: nextBadge.workersAlive };
  if (prevBadge == null) return { republish: true, reason: 'first-badge', next: nextBadge.workersAlive };
  if (prevBadge.workersAlive === nextBadge.workersAlive && prevBadge.badge === nextBadge.badge) {
    return { republish: false, reason: 'unchanged' };
  }
  return { republish: true, reason: 'workersAlive-changed', prev: prevBadge.workersAlive, next: nextBadge.workersAlive, badge: nextBadge.badge };
}

/** S112: אחרי force-live על snapshot ישן — peer מרענן */
function staleSnapshotAutorefreshPlan(freshnessGate, opts = {}) {
  if (!freshnessGate?.forceLiveMesh && freshnessGate?.reason !== 'snapshot-stale') {
    return { refresh: false, reason: 'fresh-or-absent' };
  }
  return {
    refresh: true, reason: 'stale-force-live',
    paths: opts.paths || ['/api/health', '/api/sla', '/api/trust', '/api/address'],
  };
}

/** S113: canaries מפרסמים attestDigest ב-beat */
function shardAttestGossipPlan(meta, beat, opts = {}) {
  if (!meta || (meta.stage !== 'canary' && meta.stage !== 'canary-sharded')) {
    return { gossip: false, reason: 'not-canary-stage' };
  }
  const id = beat?.id || opts.localId;
  const isCanary = (Array.isArray(meta.canaries) && meta.canaries.includes(id))
    || meta.canary?.id === id;
  if (!isCanary) return { gossip: false, reason: 'not-canary-worker' };
  const digest = meta.digest || opts.digest;
  if (!digest) return { gossip: false, reason: 'no-digest' };
  return { gossip: true, attestDigest: digest, worker: id, reason: 'shard-attest-gossip' };
}

/** S114: continuum escrow release → earnings bridge */
function continuumEarningsBridgePlan(release, settlement, opts = {}) {
  if (!release?.shouldSettle) return { bridge: false, reason: 'no-release' };
  const digest = settlement?.plan?.digest || settlement?.digest || null;
  if (!digest && !opts.allowWithoutDigest) return { bridge: false, reason: 'no-settlement' };
  return { bridge: true, reason: 'continuum-earnings-bridge', settlementDigest: digest, fromSettlement: true };
}

/** S115: מבקרים לא תופסים SLA-HA לבד (משקל מופחת) */
function visitorBeatTtlCapPlan(workers, opts = {}) {
  const now = opts.now || Date.now();
  const ttl = opts.ttlMs || WORKER_TTL_MS;
  const list = Object.values(workers || {}).filter((w) => w && (now - (w.ts || 0)) < ttl);
  const isVis = (w) => !!(w.visitor || w.tab || String(w.id || '').startsWith('v:'));
  const visitors = list.filter(isVis);
  const nodes = list.filter((w) => !isVis(w));
  const vw = opts.visitorWeight ?? 0.35;
  const weightedAlive = nodes.length + visitors.length * vw;
  const ha = nodes.length >= 2 || (nodes.length >= 1 && weightedAlive >= 2);
  return {
    workersAliveRaw: list.length,
    nodesAlive: nodes.length,
    visitorsAlive: visitors.length,
    weightedAlive: +weightedAlive.toFixed(2),
    target: ha ? 'ha' : (nodes.length === 1 ? 'single' : (visitors.length ? 'visitor-only' : 'cold')),
    visitorsCapped: true,
    note: 'visitors alone cannot claim HA',
  };
}

/** S116: אחרי failover נשארים על nsite ל-N דקות */
function failoverStickyTtlPlan(failoverState, now = Date.now(), opts = {}) {
  const stickyMs = opts.stickyMs ?? 300_000;
  if (!failoverState) return { sticky: false, preferNsite: false, reason: 'no-state' };
  let until = failoverState.stickyUntil || 0;
  if (failoverState.failover && !until) until = now + stickyMs;
  if (until && now < until) {
    return { sticky: true, preferNsite: true, stickyUntil: until, reason: 'failover-sticky' };
  }
  return { sticky: false, preferNsite: false, stickyUntil: until || null, reason: until ? 'sticky-expired' : 'no-failover' };
}

/** S117: אין פרסום living skylive בזמן dual-root */
function skyliveDualRootFreezeGate(trustState, opts = {}) {
  if (trustState?.phase === 'dual-root' && !opts.allowDualLiving) {
    return { ok: false, reason: 'skylive-dual-root-freeze', phase: 'dual-root' };
  }
  return { ok: true, phase: trustState?.phase || null };
}

/** S118: prefetch נספר בתקציב יומי (כמו receipt) */
function prefetchReceiptCapPlan(stats, cfg, nPrefetch, opts = {}) {
  const budget = cfg?.budgetUsdPerDay ?? MIND.budgetUsdPerDay ?? 5;
  const spent = stats?.usd || 0;
  const unit = opts.usdPerPrefetch ?? 0.00005;
  const n = Math.max(0, nPrefetch || 0);
  const add = +(n * unit).toFixed(6);
  if (spent + add > budget && !opts.allowOverBudget) {
    return { ok: false, reason: 'prefetch-receipt-cap', spent, add, budget, n };
  }
  return { ok: true, usd: add, spent, budget, n };
}

/** S119: tip זר חייב calling-card של A */
function fedStackCallingCardGate(tip, localStack, opts = {}) {
  if (opts.allowMissingCard) return { ok: true, reason: 'skipped' };
  const card = tip?.callingCard || tip?.card || null;
  const localCard = localStack?.callingCard || opts.localCallingCard || null;
  if (!card) return { ok: false, reason: 'fed-calling-card-missing' };
  if (opts.requireMatch && localCard && card !== localCard) {
    return { ok: false, reason: 'fed-calling-card-mismatch', card, localCard };
  }
  return { ok: true, callingCard: card };
}

/** S120: קריאות canary מ-replicas בלבד */
function canaryReadReplicaFanoutPlan(meta, peers, localId, opts = {}) {
  if (!meta || meta.stage === 'full' || meta.stage === 'solo' || meta.stage === 'rolled-back') {
    return { fanout: 'all', peers: (peers || []).slice(), reason: meta?.stage || 'no-canary' };
  }
  const list = (peers || []).filter(Boolean);
  const canaries = new Set(meta.canaries || (meta.canary?.id ? [meta.canary.id] : []));
  const replicas = list.filter((p) => !canaries.has(p));
  return {
    fanout: 'replicas',
    peers: replicas.length ? replicas : list.filter((p) => p !== localId),
    canaries: [...canaries],
    reason: replicas.length ? 'canary-read-replicas' : 'fallback-non-local',
  };
}

/** S121: rollback/seed-fail מסיר calling-card אם הגיע מה-seed */
function cardRevokeOnSeedFailPlan(seedBody, callingCard, opts = {}) {
  if (!opts.rolledBack && !opts.failed) return { revoke: false, reason: 'not-failed' };
  if (!callingCard) return { revoke: false, reason: 'no-card' };
  if (seedBody?.url && callingCard === seedBody.url) {
    return { revoke: true, reason: 'seed-card-revoke', url: callingCard };
  }
  if (opts.forceRevoke) return { revoke: true, reason: 'forced', url: callingCard };
  return { revoke: false, reason: 'card-not-from-seed' };
}

/** S122: quotes עם מס lag → שוק קבלות */
function lagTaxQuoteMarketPlan(quote, opts = {}) {
  if (!quote?.id) return { market: false, reason: 'no-quote' };
  const tax = quote.lagTax;
  if (!tax || tax.tier === 'full' || !(tax.mult > 1)) {
    return { market: !!opts.alwaysMarket, reason: 'no-lag-tax', quoteId: quote.id };
  }
  return {
    market: true, reason: 'lag-tax-quote-market', quoteId: quote.id,
    usd: tax.usd ?? quote.price?.usd ?? (typeof quote.price === 'number' ? quote.price : 0),
    tier: tax.tier, receiptKind: 'lag-tax-quote',
  };
}

/** S123: /api/doors/living — איחוד badge+failover+pin (+ S125–S136) */
function livingSlaDoorCompose(parts = {}) {
  return {
    v: 1, living: true, trustServer: false, staticOnly: false,
    health: parts.health || null,
    workersAlive: parts.health?.workersAlive ?? parts.workersAlive ?? null,
    failover: parts.failover || null,
    pin: parts.pin || null,
    sticky: parts.sticky || null,
    visitorCap: parts.visitorCap || null,
    dial: parts.dial || null,
    dialReceipt: parts.dialReceipt || null,
    fullApp: parts.fullApp || null,
    merkle: parts.merkle || null,
    continuum: parts.continuum || null,
    organism: parts.organism || null,
    census: parts.census || null,
    passport: parts.passport || null,
    doors: parts.doors || ['S99-S124', 'S125-S136', 'S137-S150', 'ADO', 'Vault', 'SpaceHop'],
    ts: parts.ts || Date.now(),
  };
}

/** S124: בלי mesh ובלי snap טרי → UI degraded */
function zeroPeerDegradedUiPlan(opts = {}) {
  const workers = opts.workersAlive ?? 0;
  if (workers > 0 || opts.meshAlive) return { degraded: false, profile: 'full', reason: 'mesh-alive' };
  if (opts.hasSnap && opts.snapFresh !== false) return { degraded: false, profile: 'snapshot', reason: 'fresh-snap' };
  return { degraded: true, profile: 'degraded', reason: 'zero-peer-no-fresh-snap', genome: 'degraded' };
}

/** LWW לפי HLC — נפילה לשדות סקלריים / כש-KV_MERGE='lww' */
function lwwPickRecord(a, b) {
  if (!a) return { winner: b, loser: null, reason: 'only-b' };
  if (!b) return { winner: a, loser: null, reason: 'only-a' };
  const ta = Number(a.hlc ?? a.ts ?? 0);
  const tb = Number(b.hlc ?? b.ts ?? 0);
  if (tb > ta) return { winner: b, loser: a, reason: 'b-newer-hlc' };
  if (ta > tb) return { winner: a, loser: b, reason: 'a-newer-hlc' };
  const da = String(a.digest ?? xiCanon(a.v) ?? '');
  const db = String(b.digest ?? xiCanon(b.v) ?? '');
  return da >= db
    ? { winner: a, loser: b, reason: 'tie-break-digest' }
    : { winner: b, loser: a, reason: 'tie-break-digest' };
}

function isPlainObject(v) {
  return !!v && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Buffer) && Object.getPrototypeOf(v) === Object.prototype;
}

/**
 * מיזוג חכם לערכי לקוח:
 * - אובייקטים: איחוד מפתחות; שדות רק בצד אחד נשמרים; התנגשות סקלרית → צד חדש יותר (HLC);
 *   התנגשות אובייקט־באובייקט → רקורסיה.
 * - מערכים: איחוד ייחודי לפרימיטיבים; אחרת LWW על המערך.
 * - אחר: LWW.
 */
function smartMergeValues(va, vb, opts = {}) {
  const preferB = opts.preferB !== false; // B = newer side when scalars conflict
  if (va === undefined || va === null) return { v: vb, reason: 'only-b', mergedFields: 0, conflictFields: 0 };
  if (vb === undefined || vb === null) return { v: va, reason: 'only-a', mergedFields: 0, conflictFields: 0 };
  if (xiCanon(va) === xiCanon(vb)) return { v: va, reason: 'equal', mergedFields: 0, conflictFields: 0 };

  if (isPlainObject(va) && isPlainObject(vb)) {
    const out = {};
    const keys = new Set([...Object.keys(va), ...Object.keys(vb)]);
    let mergedFields = 0, conflictFields = 0;
    for (const k of keys) {
      const hasA = Object.prototype.hasOwnProperty.call(va, k);
      const hasB = Object.prototype.hasOwnProperty.call(vb, k);
      if (hasA && !hasB) { out[k] = va[k]; mergedFields++; continue; }
      if (!hasA && hasB) { out[k] = vb[k]; mergedFields++; continue; }
      if (xiCanon(va[k]) === xiCanon(vb[k])) { out[k] = va[k]; continue; }
      if (isPlainObject(va[k]) && isPlainObject(vb[k])) {
        const sub = smartMergeValues(va[k], vb[k], opts);
        out[k] = sub.v;
        mergedFields += sub.mergedFields;
        conflictFields += sub.conflictFields;
      } else if (Array.isArray(va[k]) && Array.isArray(vb[k]) && va[k].every((x) => typeof x !== 'object') && vb[k].every((x) => typeof x !== 'object')) {
        const seen = new Set();
        const arr = [];
        for (const x of [...va[k], ...vb[k]]) {
          const c = xiCanon(x);
          if (seen.has(c)) continue;
          seen.add(c);
          arr.push(x);
        }
        out[k] = arr;
        mergedFields++;
        conflictFields++;
      } else {
        out[k] = preferB ? vb[k] : va[k];
        conflictFields++;
      }
    }
    return { v: out, reason: 'smart-object', mergedFields, conflictFields };
  }

  if (Array.isArray(va) && Array.isArray(vb) && va.every((x) => typeof x !== 'object') && vb.every((x) => typeof x !== 'object')) {
    const seen = new Set();
    const arr = [];
    for (const x of [...va, ...vb]) {
      const c = xiCanon(x);
      if (seen.has(c)) continue;
      seen.add(c);
      arr.push(x);
    }
    return { v: arr, reason: 'smart-array-union', mergedFields: 1, conflictFields: 1 };
  }

  return { v: preferB ? vb : va, reason: 'lww-scalar', mergedFields: 0, conflictFields: 1 };
}

/** מיזוג שני רשומות {v,ts,hlc} — smart כברירת מחדל */
function smartMergeRecords(a, b, opts = {}) {
  if (!a) return { record: b, reason: 'only-b' };
  if (!b) return { record: a, reason: 'only-a' };
  const policy = opts.policy || KV_MERGE || 'smart';
  if (policy === 'lww') {
    const pick = lwwPickRecord(a, b);
    return { record: pick.winner, reason: pick.reason, policy: 'lww' };
  }
  const ta = Number(a.hlc ?? a.ts ?? 0);
  const tb = Number(b.hlc ?? b.ts ?? 0);
  const preferB = tb >= ta;
  const newer = preferB ? b : a;
  const older = preferB ? a : b;
  const sm = smartMergeValues(older.v, newer.v, { preferB: true });
  const ts = Math.max(ta, tb);
  const record = {
    v: sm.v,
    ts,
    hlc: ts,
    digest: hex(sha256(Buffer.from('smart:' + xiCanon(sm.v)))).slice(0, 16),
    merge: { policy: 'smart', reason: sm.reason, conflictFields: sm.conflictFields, mergedFields: sm.mergedFields },
    writer: newer.writer || older.writer,
  };
  return { record, reason: sm.reason, policy: 'smart', conflictFields: sm.conflictFields };
}

/** מיזוג שני כותבים — smart (שדות) או lww לפי opts.policy / KV_MERGE */
function dualWriterLwwMerge(mapA, mapB, opts = {}) {
  const policy = opts.policy || KV_MERGE || 'smart';
  const keys = new Set([...Object.keys(mapA || {}), ...Object.keys(mapB || {})]);
  const store = {};
  let conflicts = 0;
  let smartFieldConflicts = 0;
  const resolved = [];
  for (const k of keys) {
    const a = mapA?.[k], b = mapB?.[k];
    if (a && b) {
      const sameVal = xiCanon(a.v) === xiCanon(b.v);
      if (!sameVal) conflicts++;
      if (policy === 'lww') {
        const pick = lwwPickRecord(a, b);
        store[k] = pick.winner;
        resolved.push({ key: k, reason: pick.reason, conflict: !sameVal, policy: 'lww' });
      } else {
        const m = smartMergeRecords(a, b, { policy: 'smart' });
        store[k] = m.record;
        smartFieldConflicts += m.conflictFields || 0;
        resolved.push({ key: k, reason: m.reason, conflict: !sameVal, policy: 'smart', conflictFields: m.conflictFields || 0 });
      }
    } else {
      store[k] = a || b;
    }
  }
  return {
    ok: true, n: keys.size, conflicts, smartFieldConflicts, store,
    resolved: resolved.slice(0, opts.limit || 64),
    policy: policy === 'lww' ? 'last-write-wins-hlc' : 'smart-field-merge',
    note: policy === 'lww'
      ? 'Whole-value LWW via HLC'
      : 'Smart field merge for objects; scalar field conflicts prefer newer HLC. Identity recovery remains SYNC_ID / Shamir only.',
  };
}

/** Soak מקומי: N רשומות + מיזוג דו־כותב */
function soakRecordsPlan(n, opts = {}) {
  const N = Math.max(1, Math.min(100_000, n | 0));
  const storeA = {}, storeB = {};
  for (let i = 0; i < N; i++) {
    const hlc = hlcNow();
    const key = 'rec:' + i;
    const base = { i, tag: opts.tag || 'soak', blob: String(i % 97) };
    storeA[key] = {
      v: base, hlc, ts: hlc,
      digest: hex(sha256(Buffer.from('A:' + key + ':' + xiCanon(base)))).slice(0, 16),
      writer: 'A',
    };
    // ~15% קונפליקט: B כותב ערך אחר עם HLC חדש יותר
    if (i % 7 === 0) {
      const h2 = hlcNow();
      const vB = { ...base, tag: 'B-wins', conflict: true };
      storeB[key] = {
        v: vB, hlc: h2, ts: h2,
        digest: hex(sha256(Buffer.from('B:' + key + ':' + xiCanon(vB)))).slice(0, 16),
        writer: 'B',
      };
    } else if (i % 11 === 0) {
      // B ישן יותר — A צריך לנצח
      storeB[key] = {
        v: { ...base, tag: 'B-stale' }, hlc: hlc - 1, ts: hlc - 1,
        digest: hex(sha256(Buffer.from('Bstale:' + key))).slice(0, 16),
        writer: 'B',
      };
    } else {
      storeB[key] = storeA[key];
    }
  }
  const merged = dualWriterLwwMerge(storeA, storeB, { ...opts, policy: opts.policy || KV_MERGE || 'smart' });
  return {
    ok: merged.ok && merged.n === N,
    n: N, conflicts: merged.conflicts, policy: merged.policy,
    sample: Object.keys(merged.store).slice(0, 3).map((k) => ({ key: k, v: merged.store[k].v })),
    merge: { n: merged.n, conflicts: merged.conflicts, smartFieldConflicts: merged.smartFieldConflicts },
  };
}

/**
 * UX נפילה — הודעות לאדם (APK/לקוח) כשממסרים חלשים/למטה.
 * לא מחליף רשת; מגדיר מה המשתמש רואה ומה מותר מקומית.
 */
function relayOutageUxPlan(healthOrCount, opts = {}) {
  const lang = opts.lang === 'en' ? 'en' : 'he';
  let relaysUp = 0;
  if (Array.isArray(healthOrCount)) relaysUp = healthOrCount.filter((r) => (r.phi ?? 0) < 8).length;
  else if (healthOrCount && typeof healthOrCount === 'object') {
    relaysUp = healthOrCount.relaysUp ?? healthOrCount.nOk ?? 0;
  } else relaysUp = Number(healthOrCount) || 0;
  const minOk = opts.minRelays ?? 2;
  const base = {
    relaysUp, minOk, canReadLocal: true, ts: opts.now || Date.now(),
    trustServer: false,
  };
  if (relaysUp >= minOk) {
    return {
      ...base, ok: true, degraded: false, code: 'RELAYS_OK', profile: 'full',
      canWriteNetwork: true,
      userMessage: lang === 'en' ? 'Sync network is healthy.' : 'רשת הסנכרון תקינה.',
      userAction: null,
    };
  }
  if (relaysUp <= 0) {
    return {
      ...base, ok: false, degraded: true, code: 'RELAYS_DOWN', profile: 'degraded',
      canWriteNetwork: false,
      userMessage: lang === 'en'
        ? 'No connection to the sync network. Your data on this device is still readable. New changes stay on this device until the internet/sync path returns.'
        : 'אין חיבור לרשת הסנכרון. הנתונים במכשיר הזה עדיין זמינים לקריאה. שינויים חדשים נשמרים במכשיר עד שהחיבור יחזור.',
      userAction: lang === 'en' ? 'Check internet and retry. Do not reinstall — that will not restore the cloud ID.' : 'בדוק אינטרנט ונסה שוב. אל תתקין מחדש — זה לא ישחזר את מזהה הענן.',
    };
  }
  return {
    ...base, ok: false, degraded: true, code: 'RELAYS_WEAK', profile: 'degraded',
    canWriteNetwork: true,
    userMessage: lang === 'en'
      ? 'Sync network is weak. Some updates may be delayed. Keep the app open; it will retry automatically.'
      : 'רשת הסנכרון חלשה. חלק מהעדכונים עלולים להתעכב. השאר את האפליקציה פתוחה — היא תנסה שוב אוטומטית.',
    userAction: lang === 'en' ? 'Wait or switch network.' : 'המתן או החלף רשת.',
  };
}

/**
 * ערכת שחזור — בלי SYNC_ID בפנים.
 * כנה: בלי סוד (קובץ cloud.js / רסיסים) אי אפשר לפענח נתונים פרטיים.
 */
function recoveryKitPlan(parts = {}) {
  const kit = {
    v: 1,
    kind: 'skycloud-recovery-kit',
    recoverableWithoutSecret: false,
    note: 'Does NOT include SYNC_ID. Public address alone cannot decrypt private cloud data.',
    pubkey: parts.pubkey || null,
    address: parts.address || null,
    tipDigest: parts.tipDigest || null,
    siteSlug: parts.siteSlug || null,
    flockId: parts.flockId || null,
    exportedAt: parts.now || Date.now(),
  };
  kit.fingerprint = hex(sha256(Buffer.from('recovery-kit:v1:' + xiCanon({
    pubkey: kit.pubkey, address: kit.address, tip: kit.tipDigest,
  })))).slice(0, 32);
  return {
    ok: true, kit,
    nextStepsHe: [
      'שמור עותק של cloud.js עם ה-SYNC_ID, או רסיסי Shamir (--shares).',
      'אם איבדת גם ID וגם רסיסים — הנתונים המוצפנים אינם ניתנים לשחזור מהכתובת הציבורית.',
      'ערכת שחזור זו רק מזהה את הענן (כתובת/pubkey) — לא פותחת אותו.',
    ],
  };
}

function lostIdRecoveryGate(opts = {}) {
  // זהות הענן = רק SYNC_ID או רסיסי Shamir. אין מסלול שחזור אחר (לא URL, לא kit בלבד, לא סיסמה חיצונית).
  if (opts.hasCloudJsWithId || opts.hasSyncIdString) {
    return {
      ok: true, reason: 'paste-or-copy-id', action: 'use-§2.5-method-A-or-B',
      onlyAllowed: ['SYNC_ID', 'cloud.js-with-id'],
    };
  }
  if (opts.hasShamirShares) {
    return {
      ok: true, reason: 'recover-shares', action: 'node cloud.js --recover … --write',
      onlyAllowed: ['Shamir-shares'],
    };
  }
  if (opts.hasRecoveryKitOnly) {
    return {
      ok: false, reason: 'kit-identifies-not-decrypts',
      onlyAllowed: ['SYNC_ID', 'Shamir-shares'],
      userMessageHe: 'ערכת השחזור מזהה את הענן אבל לא מכילה את המפתח. צריך cloud.js עם SYNC_ID או רסיסי Shamir בלבד — אין דרך אחרת.',
      userMessageEn: 'Recovery kit identifies the cloud but cannot decrypt. Only SYNC_ID or Shamir shares — nothing else.',
    };
  }
  return {
    ok: false, reason: 'unrecoverable-without-secret',
    onlyAllowed: ['SYNC_ID', 'Shamir-shares'],
    userMessageHe: 'בלי SYNC_ID ובלי רסיסי Shamir — אי אפשר לשחזר. אין תחליף (לא כתובת ציבורית, לא kit, לא סיסמה אחרת).',
    userMessageEn: 'Without SYNC_ID and without Shamir shares, recovery is impossible. No alternate path.',
  };
}

/** תכנון התראת מפעיל (Telegram / webhook) — בלי לשלוח ברשת ב־plan */
function alertEventPlan(kind, detail = {}, opts = {}) {
  const ev = {
    v: 1,
    kind: String(kind || 'ops'),
    code: detail.code || null,
    message: String(detail.message || detail.userMessage || '').slice(0, 500),
    relaysUp: detail.relaysUp ?? null,
    device: detail.device || null,
    ts: opts.now || Date.now(),
  };
  ev.digest = hex(sha256(Buffer.from('alert:v1:' + xiCanon(ev)))).slice(0, 24);
  return ev;
}

function alertThrottleGate(lastSentTs, now, minIntervalMs) {
  const min = minIntervalMs ?? ALERTS.minIntervalMs ?? 300_000;
  const t = now || Date.now();
  if (!lastSentTs) return { send: true, reason: 'first', minIntervalMs: min };
  const wait = min - (t - lastSentTs);
  if (wait > 0) return { send: false, reason: 'throttled', waitMs: wait, minIntervalMs: min };
  return { send: true, reason: 'interval-ok', minIntervalMs: min };
}

/** פרסום אפליקציה מלאה — חוסם tip חלקי (+ S126 merkle root) */
function fullAppPublishGate(expectedPaths, publishedPaths, opts = {}) {
  const exp = [...new Set((expectedPaths || []).map((p) => (Array.isArray(p) ? p[0] : p)))];
  const got = new Set((publishedPaths || []).map((p) => (Array.isArray(p) ? p[0] : p)));
  const missing = exp.filter((p) => !got.has(p));
  const minRatio = opts.minRatio ?? 1;
  const ratio = exp.length ? (exp.length - missing.length) / exp.length : 1;
  if (opts.allowPartial) return { ok: true, reason: 'allowed-partial', missing, ratio, nMissing: missing.length };
  if (missing.length && ratio < minRatio) {
    return { ok: false, reason: 'full-app-incomplete', missing: missing.slice(0, 50), nMissing: missing.length, ratio };
  }
  if (opts.requireMerkle && opts.merkleRoot && opts.expectedMerkle && opts.merkleRoot !== opts.expectedMerkle) {
    return { ok: false, reason: 'merkle-mismatch', merkleRoot: opts.merkleRoot, expectedMerkle: opts.expectedMerkle, nMissing: 0, ratio: 1 };
  }
  return { ok: true, reason: 'complete', n: got.size, ratio: 1, nMissing: 0, merkleRoot: opts.merkleRoot || null };
}

/** חיבור ייעודי למבקר — dial ל-side-effects דרך mesh (לא Node על השער) */
function dedicatedMeshDialPlan(opts = {}) {
  return {
    v: 1,
    dial: true,
    channel: opts.channel || ('dial:' + (opts.handle || 'live')),
    mode: 'side-effect-node',
    trustServer: false,
    staticOnly: false,
    requires: ['cloud-web', 'omega-mesh', 'bridge-or-visitor-or-full-node'],
    note: 'Dedicated Ω dial: side-effects via mesh peers / visitor workers / LOCAL_BRIDGE — nsite stays shell',
  };
}

// ═══════════ SpaceMesh-Ν: NASA-grade DTN / CGR / Orbital Dance ═══════════
// מורשת: Delay-Tolerant Networking (RFC 9171), Contact Graph Routing (ION/JPL).
// כנה: אין שיגור לוויין מהקוד — יש מתמטיקת מסלול+מגע+custody מעל רשת מבוזרת.

/** אנומליה ממוצעת (Keplerian mean anomaly) למסלול-לוגי slot */
function orbitalMeanAnomaly(slot, tMs, opts = {}) {
  const n = Math.max(1, opts.slots || SPACE_MESH.slots || 12);
  const period = Math.max(1, opts.periodMs || SPACE_MESH.periodMs || 5_400_000);
  const phase0 = (Number(slot) % n) / n * 2 * Math.PI;
  const M = phase0 + 2 * Math.PI * ((tMs || Date.now()) / period);
  const tau = 2 * Math.PI;
  return ((M % tau) + tau) % tau;
}

/** נראות מסלול — proxy ל-elevation (cos M); visible כשמעל הסף */
function orbitalVisibility(slot, tMs, opts = {}) {
  const M = orbitalMeanAnomaly(slot, tMs, opts);
  const elev = Math.cos(M);
  const minElev = opts.minElev ?? SPACE_MESH.minElev ?? 0.12;
  return { slot: Number(slot), anomaly: +M.toFixed(6), elev: +elev.toFixed(6), visible: elev > minElev, minElev };
}

/** תוכנית מגעים (contact plan) בין slots נראים — בסיס ל-CGR */
function buildOrbitalContactPlan(opts = {}) {
  const now = opts.now || Date.now();
  const slots = opts.slots || SPACE_MESH.slots || 12;
  const horizon = opts.horizonMs || (opts.periodMs || SPACE_MESH.periodMs || 5_400_000) / 4;
  const step = opts.stepMs || Math.max(5_000, horizon / 24);
  const contacts = [];
  for (let t = now; t <= now + horizon; t += step) {
    const vis = [];
    for (let s = 0; s < slots; s++) {
      const v = orbitalVisibility(s, t, opts);
      if (v.visible) vis.push(v);
    }
    // כל זוג נראה באותו חלון = מגע לוגי (inter-satellite / ISL proxy)
    for (let i = 0; i < vis.length; i++) {
      for (let j = i + 1; j < vis.length; j++) {
        contacts.push({
          from: 'orb:' + vis[i].slot, to: 'orb:' + vis[j].slot,
          start: t, end: t + step, elev: Math.min(vis[i].elev, vis[j].elev),
          kind: 'isl',
        });
        contacts.push({
          from: 'orb:' + vis[j].slot, to: 'orb:' + vis[i].slot,
          start: t, end: t + step, elev: Math.min(vis[i].elev, vis[j].elev),
          kind: 'isl',
        });
      }
    }
    // ground gateway ↔ visible slots
    for (const v of vis) {
      contacts.push({ from: 'ground', to: 'orb:' + v.slot, start: t, end: t + step, elev: v.elev, kind: 'uplink' });
      contacts.push({ from: 'orb:' + v.slot, to: 'ground', start: t, end: t + step, elev: v.elev, kind: 'downlink' });
    }
  }
  return { v: 1, now, horizonMs: horizon, nContacts: contacts.length, contacts, note: 'logical LEO contact plan — control plane only' };
}

/**
 * Contact Graph Routing (CGR-inspired): Dijkstra על גרף מגעים תלוי-זמן.
 * מחזיר מסלול ground→…→ground או null אם אין מגע באופק.
 */
function contactGraphRoute(plan, src = 'ground', dst = 'ground', opts = {}) {
  const now = opts.now || plan?.now || Date.now();
  const contacts = (plan?.contacts || []).filter((c) => c.end > now);
  if (src === dst) return { ok: true, path: [src], delayMs: 0, reason: 'same-node' };
  // state: node -> { arrTime, prev, via }
  const arr = new Map([[src, now]]);
  const prev = new Map();
  const via = new Map();
  const q = [src];
  while (q.length) {
    q.sort((a, b) => (arr.get(a) || Infinity) - (arr.get(b) || Infinity));
    const u = q.shift();
    const tU = arr.get(u);
    if (u === dst) break;
    for (const c of contacts) {
      if (c.from !== u) continue;
      if (c.end <= tU) continue;
      const depart = Math.max(tU, c.start);
      if (depart >= c.end) continue;
      const arrive = depart + Math.max(1, opts.propMs || 40); // light-ish hop
      const prevArr = arr.get(c.to);
      if (prevArr == null || arrive < prevArr) {
        arr.set(c.to, arrive);
        prev.set(c.to, u);
        via.set(c.to, c);
        if (!q.includes(c.to)) q.push(c.to);
      }
    }
  }
  if (!arr.has(dst)) return { ok: false, reason: 'no-contact-path', src, dst };
  const path = [];
  let cur = dst;
  while (cur != null) { path.unshift(cur); cur = prev.get(cur); }
  return {
    ok: true, path, delayMs: arr.get(dst) - now,
    hops: path.length - 1, src, dst,
    note: 'CGR-inspired time-varying Dijkstra',
  };
}

/** ריקוד לוויינים — בחירת נתיבים פעילים + גיוון קרקע/space לפי פאזה */
function satelliteDancePlan(relays, spaceRelays, tMs, opts = {}) {
  const now = tMs || Date.now();
  const slots = opts.slots || SPACE_MESH.slots || 12;
  const period = opts.dancePeriodMs || SPACE_MESH.dancePeriodMs || 90_000;
  const phase = Math.floor(now / period) % Math.max(1, slots);
  const visible = [];
  for (let s = 0; s < slots; s++) {
    const v = orbitalVisibility(s, now, opts);
    if (v.visible) visible.push(v);
  }
  const terr = (relays || []).map((u, i) => ({ url: u, segment: 'terrestrial', slot: i % slots }));
  const space = (spaceRelays || SPACE_MESH.spaceClass || []).map((u, i) => ({ url: u, segment: 'space', slot: (i + phase) % slots }));
  // ריקוד: מזיזים העדפה כך ש-space slots "עוברים" ביניהם
  const ranked = [...space, ...terr].map((r) => {
    const vis = orbitalVisibility(r.slot, now, opts);
    const danceBoost = r.segment === 'space' ? (1 + 0.5 * Math.cos(orbitalMeanAnomaly(r.slot + phase, now, opts))) : 1;
    const score = (vis.visible ? 1.2 : 0.4) * danceBoost * (r.segment === 'space' ? 1.15 : 1);
    return { ...r, elev: vis.elev, visible: vis.visible, score: +score.toFixed(4), phase };
  }).sort((a, b) => b.score - a.score);
  const active = ranked.filter((r) => r.visible || r.segment === 'terrestrial').slice(0, opts.k || 6);
  const diversity = multipathDiversityScore(active.map((a) => a.segment + ':' + a.slot));
  return {
    v: 1, phase, periodMs: period, visibleSlots: visible.map((v) => v.slot),
    active, ranked: ranked.slice(0, 12), diversity,
    alwaysOn: active.length >= 2,
    note: 'orbital dance handover — multipath terrestrial+space class',
  };
}

/** ציון גיוון נתיבים (Hamming / Jaccard על תוויות) */
function multipathDiversityScore(labels) {
  const labs = (labels || []).map(String);
  if (labs.length < 2) return { score: 0, n: labs.length, pairs: 0 };
  let sum = 0, pairs = 0;
  for (let i = 0; i < labs.length; i++) {
    for (let j = i + 1; j < labs.length; j++) {
      pairs++;
      const a = new Set(labs[i].split(/[:|/]/));
      const b = new Set(labs[j].split(/[:|/]/));
      const inter = [...a].filter((x) => b.has(x)).length;
      const union = new Set([...a, ...b]).size || 1;
      sum += 1 - inter / union;
    }
  }
  return { score: +(sum / pairs).toFixed(4), n: labs.length, pairs };
}

/** DTN Bundle — מטען עם custody, TTL, רמזי מסלול (RFC 9171-inspired) */
function dtnBundlePlan(payload, opts = {}) {
  const body = Buffer.isBuffer(payload) ? payload : Buffer.from(typeof payload === 'string' ? payload : JSON.stringify(payload ?? null), 'utf8');
  const id = hex(sha256(Buffer.concat([Buffer.from('dtn:v1:'), body, Buffer.from(String(opts.now || Date.now()))]))).slice(0, 32);
  const ttl = opts.ttlMs ?? SPACE_MESH.custodyTtlMs ?? 3_600_000;
  const created = opts.now || Date.now();
  // פיצול RS קל ל-custody רב-נתיבי
  let fragments = null;
  if (opts.erasure !== false && body.length >= 16) {
    const k = opts.k || 4;
    const m = opts.m || 2;
    const pad = Buffer.concat([body, Buffer.alloc((k - (body.length % k)) % k)]);
    const shardLen = pad.length / k;
    const shards = [];
    for (let i = 0; i < k; i++) shards.push(pad.subarray(i * shardLen, (i + 1) * shardLen));
    const parity = rsEncode(shards, m);
    fragments = {
      k, m, shardLen, total: pad.length, origLen: body.length,
      digests: [...shards, ...parity].map((s) => hex(sha256(s)).slice(0, 16)),
    };
  }
  return {
    v: 1, id, created, expire: created + ttl,
    custody: true, priority: opts.priority || 'bulk',
    src: opts.src || 'ground', dst: opts.dst || 'ground',
    pathHint: opts.pathHint || null,
    size: body.length, fragments,
    digest: hex(sha256(body)).slice(0, 40),
    note: 'DTN custody bundle — store-and-forward when path dark',
  };
}

/** האם bundle עדיין ב-custody / פג */
function dtnCustodyGate(bundle, now = Date.now()) {
  if (!bundle?.id) return { ok: false, reason: 'no-bundle' };
  if (bundle.expire && now > bundle.expire) return { ok: false, reason: 'custody-expired', expired: true };
  if (bundle.delivered) return { ok: true, reason: 'delivered', deliver: false };
  return { ok: true, reason: 'custody-hold', deliver: true, remainMs: bundle.expire ? bundle.expire - now : null };
}

/**
 * ערוץ "צבאי" — ratchet קדימה (forward secrecy) מעל ECDH+SHA256.
 * כל הודעה גוזרת מפתח חדש; Compromise של מפתח נוכחי לא חושף עבר.
 */
function militaryRatchetStep(chainKey, dhShared, opts = {}) {
  const ck = Buffer.isBuffer(chainKey) ? chainKey : Buffer.from(String(chainKey || ''), 'hex');
  const dh = Buffer.isBuffer(dhShared) ? dhShared : Buffer.from(String(dhShared || ''), 'utf8');
  const info = Buffer.from(opts.info || 'sc-mil-ratchet-v1');
  const material = sha256(Buffer.concat([Buffer.from([0x01]), ck.length ? ck : Buffer.alloc(32), dh, info]));
  const nextCk = sha256(Buffer.concat([Buffer.from([0x02]), material, info]));
  const msgKey = sha256(Buffer.concat([Buffer.from([0x03]), material, info]));
  return {
    chainKey: hex(nextCk),
    msgKey: hex(msgKey),
    epoch: (opts.epoch || 0) + 1,
    fs: true,
    note: 'forward-secrecy ratchet — compromise ≠ rewind',
  };
}

/** חותמת מסלול רב-עננית: terrestrial ∪ space ∪ dial ∪ ADO */
function spaceMeshManifest(dance, route, opts = {}) {
  return {
    v: 1,
    mode: 'multi-cloud-space',
    trustServer: false,
    alwaysOn: !!(dance?.alwaysOn || opts.alwaysOn),
    dancePhase: dance?.phase ?? null,
    diversity: dance?.diversity || null,
    activePaths: (dance?.active || []).map((a) => ({ segment: a.segment, slot: a.slot, url: a.url, score: a.score })),
    cgr: route ? { ok: route.ok, path: route.path, delayMs: route.delayMs, hops: route.hops } : null,
    custody: true,
    ado: opts.ado || null,
    vaultRoot: opts.vaultRoot || null,
    ecMode: opts.ecMode || 'eventual-slow',
    note: 'SpaceMesh control plane — DTN+CGR+dance over decentralized relays; physical sat ISP is external last-mile',
    ts: opts.now || Date.now(),
  };
}

// ═══════════ Immortal-Ι: שכפול עצמי חי על אנטנות הרשת ═══════════
// כנה: לא מדברים עם לוויין בודד. מזהים נתיב לווייני לפי מאפייני אינטרנט (RTT),
// ומשכפלים את הענן לכל האנטנות הציבוריות — כך הוא חי גם כשהמקור לא נגיש.

/** סיווג נתיב רשת לפי probe (לווייני / אזורי / סיב) */
function networkPathClassFromProbe(probe, opts = {}) {
  const rtt = probe?.rttMs ?? probe?.ms ?? Infinity;
  const jitter = probe?.jitterMs ?? 0;
  const satRtt = opts.satRttMs ?? IMMORTAL_MESH.satRttMs ?? 600;
  const satJit = opts.satJitterMs ?? IMMORTAL_MESH.satJitterMs ?? 80;
  if (!probe?.ok) return { class: 'dark', score: 0, rtt, jitter, satelliteLikely: false };
  const satelliteLikely = rtt >= satRtt || (rtt >= satRtt * 0.66 && jitter >= satJit);
  let cls = 'terrestrial';
  if (satelliteLikely) cls = 'satellite-path';
  else if (rtt >= 180) cls = 'regional';
  const score = probe.ok ? Math.max(0.05, Math.min(1, 1 - rtt / 2000)) : 0;
  return { class: cls, score: +score.toFixed(4), rtt, jitter, satelliteLikely, ok: true };
}

/** מלאי אנטנות — נקודות כניסה לאינטרנט הציבורי (לא לוויינים פיזיים) */
function antennaInventoryPlan(opts = {}) {
  const relays = (opts.relays || []).map((u) => ({ kind: 'relay', url: u, role: 'antenna' }));
  const blossom = (opts.blossom || []).map((u) => ({ kind: 'blossom', url: u, role: 'antenna' }));
  const nsite = (opts.nsite || []).map((u) => ({ kind: 'nsite', url: u, role: 'antenna' }));
  const space = (opts.space || []).map((u) => ({ kind: 'space-class', url: u, role: 'antenna' }));
  const all = [...nsite, ...blossom, ...space, ...relays];
  const byKind = {};
  for (const a of all) byKind[a.kind] = (byKind[a.kind] || 0) + 1;
  return {
    v: 1, n: all.length, byKind, antennas: all.slice(0, 64),
    note: 'antennas = public internet endpoints (relays/blossom/nsite); sat birds are behind ISP',
  };
}

/** tip שכפול אלמוות — digest יציב של זהות חיה (+ merkle / continuum) */
function immortalReplicaTip(parts = {}) {
  const core = {
    v: 1,
    canon: parts.canon || null,
    capsule: parts.capsuleDigest || parts.capsule || null,
    skylive: parts.skyliveDigest || parts.skylive || null,
    living: !!parts.living,
    fullApp: parts.fullAppOk !== false,
    dial: parts.dialChannel || null,
    spacePhase: parts.spacePhase ?? null,
    merkle: parts.merkleRoot || parts.merkle || null,
    continuum: parts.continuumDigest || parts.continuum || null,
    brain: parts.brainDigest || parts.brain || null,
  };
  const digest = hex(sha256(Buffer.from('immortal:tip:v1:' + xiCanon(core)))).slice(0, 40);
  return { ...core, digest, ts: parts.ts || Date.now() };
}

/** שערי הכחדה — האם הרשת עדיין מחזיקה מספיק שכפולים/אנטנות */
function antiExtinctionGate(replicaRegistry, antennaHealth, opts = {}) {
  const minR = opts.minReplicas ?? IMMORTAL_MESH.minReplicas ?? 2;
  const minA = opts.minAntennas ?? IMMORTAL_MESH.minAntennas ?? 3;
  const now = opts.now || Date.now();
  const ttl = opts.ttlMs || 180_000;
  const replicas = Object.values(replicaRegistry || {}).filter((r) => r && (now - (r.ts || 0)) < ttl);
  const tips = new Set(replicas.map((r) => r.digest).filter(Boolean));
  const ants = (antennaHealth || []).filter((a) => a && a.ok);
  const satPaths = ants.filter((a) => a.pathClass === 'satellite-path' || a.satelliteLikely);
  const ok = replicas.length >= minR && ants.length >= minA;
  return {
    ok,
    reason: ok ? 'immortal-quorum' : (replicas.length < minR ? 'extinction-replicas' : 'extinction-antennas'),
    nReplicas: replicas.length,
    nAntennas: ants.length,
    nTips: tips.size,
    nSatellitePaths: satPaths.length,
    minReplicas: minR,
    minAntennas: minA,
    extinct: !ok,
  };
}

/** תוכנית עדכון שכפולים — מי צריך למשוך tip חדש */
function replicaUpdatePlan(localTip, remoteReplicas, opts = {}) {
  const local = localTip?.digest || null;
  const remotes = Object.entries(remoteReplicas || {}).map(([id, r]) => ({ id, ...(r || {}) }));
  const stale = remotes.filter((r) => r.digest && local && r.digest !== local);
  const missing = remotes.filter((r) => !r.digest);
  const fresh = remotes.filter((r) => r.digest && r.digest === local);
  const needPush = !!(local && (stale.length || missing.length || remotes.length < (opts.minReplicas || 2)));
  return {
    localDigest: local,
    nRemote: remotes.length,
    nStale: stale.length,
    nMissing: missing.length,
    nFresh: fresh.length,
    staleIds: stale.map((r) => r.id).slice(0, 20),
    needPush,
    needPull: !!(opts.remoteTip && local && opts.remoteTip !== local),
    remoteTip: opts.remoteTip || null,
  };
}

/** תוכנית שכפול עצמי מלאה */
function selfReplicationPlan(opts = {}) {
  return {
    v: 1,
    immortal: true,
    originOptional: true,
    steps: [
      'probe-antennas',
      'classify-paths',
      'publish-full-app',
      'publish-living-snapshots',
      'publish-capsule-tip',
      'register-replica',
      'fanout-mirrors',
      'anti-extinction-check',
    ],
    tip: opts.tip || null,
    antennas: opts.antennas || null,
    pathClasses: opts.pathClasses || null,
    note: 'self-replicates across public antennas; survives unreachable origin',
  };
}

// ═══════════ ADO Superlattice: Merkle / EC / Organism / Vault / Doors S125–S136 ═══════════

/** עץ Merkle בינארי אמיתי מעל leaves (hex digests או buffers) */
function merkleTreeFromLeaves(leaves) {
  const hashLeaf = (x) => {
    const b = Buffer.isBuffer(x) ? x : Buffer.from(String(x), 'utf8');
    return hex(sha256(Buffer.concat([Buffer.from([0]), b])));
  };
  const hashNode = (a, b) => hex(sha256(Buffer.concat([Buffer.from([1]), Buffer.from(a, 'hex'), Buffer.from(b, 'hex')])));
  let level = (leaves || []).map(hashLeaf);
  if (!level.length) level = [hex(sha256(Buffer.from('merkle:empty')))];
  const layers = [level.slice()];
  while (level.length > 1) {
    const next = [];
    for (let i = 0; i < level.length; i += 2) {
      const L = level[i], R = level[i + 1] || level[i];
      next.push(hashNode(L, R));
    }
    level = next;
    layers.push(level.slice());
  }
  return { root: level[0], layers, nLeaves: (leaves || []).length };
}

function merkleProof(tree, index) {
  const layers = tree?.layers || [];
  if (!layers.length || index < 0 || index >= layers[0].length) return { ok: false, reason: 'bad-index' };
  const path = [];
  let i = index | 0;
  for (let d = 0; d < layers.length - 1; d++) {
    const sib = (i % 2 === 0) ? (i + 1) : (i - 1);
    const layer = layers[d];
    path.push({ sibling: layer[sib] || layer[i], side: i % 2 === 0 ? 'R' : 'L' });
    i = Math.floor(i / 2);
  }
  return { ok: true, index, leaf: layers[0][index], path, root: tree.root };
}

function merkleVerify(leaf, proof, root) {
  if (!proof?.path || !root) return false;
  let h = leaf;
  const hashNode = (a, b) => hex(sha256(Buffer.concat([Buffer.from([1]), Buffer.from(a, 'hex'), Buffer.from(b, 'hex')])));
  for (const step of proof.path) {
    h = step.side === 'R' ? hashNode(h, step.sibling) : hashNode(step.sibling, h);
  }
  return h === root;
}

/** S126: tip = Merkle על כל קבצי האפליקציה */
function fullAppMerkleTip(fileEntries, opts = {}) {
  const entries = (fileEntries || []).map((e) => {
    if (Array.isArray(e)) return { path: e[0], digest: e[1] };
    if (typeof e === 'string') return { path: e, digest: hex(sha256(Buffer.from(e))).slice(0, 40) };
    return { path: e.path || e.p, digest: e.digest || e.hash || hex(sha256(Buffer.from(xiCanon(e)))).slice(0, 40) };
  }).sort((a, b) => String(a.path).localeCompare(String(b.path)));
  const leaves = entries.map((e) => e.path + ':' + e.digest);
  const tree = merkleTreeFromLeaves(leaves);
  return {
    v: 1, merkleRoot: tree.root, n: entries.length, entries: entries.slice(0, opts.maxEntries || 200),
    digest: hex(sha256(Buffer.from('fullapp:merkle:v1:' + tree.root))).slice(0, 40),
  };
}

function merkleAntiEntropyDiff(localTree, remoteRoot, localLeaves) {
  if (!remoteRoot) return { sync: 'full', reason: 'no-remote' };
  if (localTree?.root === remoteRoot) return { sync: 'noop', reason: 'equal', root: remoteRoot };
  return {
    sync: 'partial', reason: 'root-mismatch',
    localRoot: localTree?.root || null, remoteRoot,
    nLocal: (localLeaves || localTree?.layers?.[0] || []).length,
    note: 'request missing leaves via merkle proofs',
  };
}

/** הרחבת סיווג נתיב ל-defer/batch (latency-aware EC) */
function satLatencyClass(probeOrClass, opts = {}) {
  const cls = typeof probeOrClass === 'string'
    ? { class: probeOrClass }
    : networkPathClassFromProbe(probeOrClass || {}, opts);
  const presets = opts.presets || ADO.lightPresets || {};
  let deferMs = 0, batchWindowMs = 250, lightPreset = 'earth';
  if (cls.class === 'satellite-path') { deferMs = 2_000; batchWindowMs = 8_000; lightPreset = 'leo'; }
  else if (cls.class === 'regional') { deferMs = 400; batchWindowMs = 1_500; lightPreset = 'earth'; }
  else if (cls.class === 'dark') { deferMs = 30_000; batchWindowMs = 60_000; lightPreset = 'mars'; }
  if (opts.preset && presets[opts.preset] != null) {
    lightPreset = opts.preset;
    deferMs = Math.max(deferMs, presets[opts.preset]);
  }
  return { ...cls, deferMs, batchWindowMs, lightPreset, ecMode: 'eventual-slow' };
}

function eventualSyncPlan(pathClass, opts = {}) {
  const lat = satLatencyClass(pathClass?.class ? pathClass : { class: pathClass || 'terrestrial', ok: true, rttMs: 40 }, opts);
  const danceOpen = !!(opts.alwaysOn || opts.contactOpen);
  const heavy = !!opts.heavy;
  if (lat.class === 'dark' && !opts.force) {
    return { action: 'hold-custody', reason: 'path-dark', deferMs: lat.deferMs, lat };
  }
  if (heavy && !danceOpen && (lat.class === 'satellite-path' || lat.deferMs >= 1000)) {
    return { action: 'defer-dtn', reason: 'await-contact-window', deferMs: lat.deferMs, batchWindowMs: lat.batchWindowMs, lat };
  }
  if (lat.deferMs > 0 && !opts.immediate) {
    return { action: 'batch', reason: 'latency-batch', deferMs: lat.deferMs, batchWindowMs: lat.batchWindowMs, lat };
  }
  return { action: 'publish-now', reason: 'path-open', deferMs: 0, lat };
}

function contactWindowShipGate(dance, opts = {}) {
  const open = !!(dance?.alwaysOn || opts.force || opts.contactOpen);
  if (!open && opts.heavy !== false) {
    return { ship: false, reason: 'contact-window-closed', phase: dance?.phase ?? null };
  }
  return { ship: true, reason: open ? 'contact-open' : 'light-ok', phase: dance?.phase ?? null };
}

function versionVectorMerge(a = {}, b = {}) {
  const out = { ...a };
  for (const [k, v] of Object.entries(b || {})) {
    const n = Number(v) || 0;
    out[k] = Math.max(Number(out[k]) || 0, n);
  }
  const dominated = Object.keys(out).every((k) => (Number(a[k]) || 0) >= (Number(out[k]) || 0))
    && Object.keys(a).length >= Object.keys(out).length;
  return { vector: out, advanced: !dominated, digest: hex(sha256(Buffer.from('vv:' + xiCanon(out)))).slice(0, 24) };
}

function replicaTipLagMs(localTip, remoteTip, opts = {}) {
  const now = opts.now || Date.now();
  if (!remoteTip?.digest) return { lagMs: 0, ok: true, reason: 'no-remote' };
  if (localTip?.digest === remoteTip.digest) return { lagMs: 0, ok: true, reason: 'equal' };
  const remoteTs = remoteTip.ts || remoteTip.hlc?.wall || 0;
  const localTs = localTip?.ts || localTip?.hlc?.wall || now;
  const lagMs = Math.max(0, Math.abs((remoteTs || now) - (localTs || now)));
  const skew = remoteTs && localTs ? Math.max(0, remoteTs - localTs) : lagMs;
  return { lagMs: skew, ok: skew < (opts.boundMs ?? ADO.replicaLagBoundMs ?? 120_000), reason: 'tip-skew', local: localTip?.digest, remote: remoteTip.digest };
}

/** ADO organism */
function brainDigestFromSelfModel(model, opts = {}) {
  const core = {
    weights: model?.weights || model?.w || null,
    genome: model?.genome || opts.genome || null,
    vitality: model?.vitality ?? null,
    layers: model?.layers || model?.nLayers || null,
  };
  return hex(sha256(Buffer.from('ado:brain:v1:' + xiCanon(core)))).slice(0, 40);
}

function organismMemberManifest(parts = {}) {
  const core = {
    v: 1,
    device: parts.device || null,
    tipDigest: parts.tipDigest || parts.tip?.digest || null,
    brainDigest: parts.brainDigest || null,
    genome: parts.genome || null,
    capabilities: parts.capabilities || ['mesh', 'brain'],
    pathClass: parts.pathClass || 'terrestrial',
    visitor: !!parts.visitor,
    node: parts.node !== false && !parts.visitor,
  };
  const digest = hex(sha256(Buffer.from('ado:member:v1:' + xiCanon(core)))).slice(0, 40);
  return { ...core, digest, ts: parts.ts || Date.now() };
}

function organismQuorumGate(members, antennaHealth, opts = {}) {
  const now = opts.now || Date.now();
  const ttl = opts.ttlMs || 180_000;
  const minB = opts.minBrains ?? ADO.minBrains ?? 2;
  const minA = opts.minAntennas ?? ADO.minAntennas ?? 3;
  const list = Object.values(members || {}).filter((m) => m && (now - (m.ts || 0)) < ttl);
  const brains = list.filter((m) => m.brain || m.brainDigest);
  const ants = (antennaHealth || []).filter((a) => a && a.ok);
  const ok = brains.length >= minB && ants.length >= minA;
  return {
    ok, extinct: !ok,
    reason: ok ? 'organism-quorum' : (brains.length < minB ? 'extinction-brains' : 'extinction-antennas'),
    nBrains: brains.length, nMembers: list.length, nAntennas: ants.length,
    minBrains: minB, minAntennas: minA,
  };
}

function partitionHealPlan(replicas, opts = {}) {
  const counts = {};
  for (const r of Object.values(replicas || {})) {
    if (!r?.digest) continue;
    counts[r.digest] = (counts[r.digest] || 0) + 1;
  }
  const ranked = Object.entries(counts).sort((a, b) => b[1] - a[1] || String(a[0]).localeCompare(b[0]));
  const winner = ranked[0] || null;
  const quorum = opts.quorum || Math.max(2, Math.ceil(Object.keys(replicas || {}).length / 2));
  return {
    tip: winner ? winner[0] : null,
    votes: winner ? winner[1] : 0,
    quorum,
    ok: !!(winner && winner[1] >= Math.min(quorum, 1)),
    reason: winner ? (winner[1] >= quorum ? 'quorum-tip' : 'plurality-tip') : 'no-tips',
    tally: Object.fromEntries(ranked.slice(0, 8)),
  };
}

function visitorBrainBootstrapPlan(opts = {}) {
  const hasCapsule = !!(opts.capsule || opts.capsuleDigest);
  const hasSdk = opts.hasSdk !== false;
  const vw = visitorWeightGovPlan({
    trust: opts.trust ?? 0.5, sla: opts.sla ?? 50, beatAgeMs: opts.beatAgeMs ?? 0,
  });
  return {
    ok: hasCapsule && hasSdk,
    reason: !hasSdk ? 'no-sdk' : (!hasCapsule ? 'no-capsule' : 'visitor-brain'),
    member: hasCapsule && hasSdk,
    visitorWeight: vw.weight,
    capabilities: ['visitor-brain', 'serveApi', 'mesh-light'],
  };
}

function extinctionHazardScore(history, opts = {}) {
  const alpha = opts.alpha ?? 0.35;
  let ewmaR = opts.seedReplicas ?? 2, ewmaA = opts.seedAntennas ?? 3;
  for (const h of history || []) {
    ewmaR = alpha * (h.nReplicas ?? ewmaR) + (1 - alpha) * ewmaR;
    ewmaA = alpha * (h.nAntennas ?? ewmaA) + (1 - alpha) * ewmaA;
  }
  const minR = opts.minReplicas ?? IMMORTAL_MESH.minReplicas ?? 2;
  const minA = opts.minAntennas ?? IMMORTAL_MESH.minAntennas ?? 3;
  const hazard = Math.max(0, Math.min(1, 1 - Math.min(ewmaR / minR, ewmaA / minA)));
  return { hazard: +hazard.toFixed(4), ewmaReplicas: +ewmaR.toFixed(2), ewmaAntennas: +ewmaA.toFixed(2), preempt: hazard >= (opts.preemptAt ?? 0.45) };
}

/** Immortal Vault */
function vaultShardPlan(blob, opts = {}) {
  const buf = Buffer.isBuffer(blob) ? blob : Buffer.from(String(blob || ''), 'utf8');
  const k = opts.k ?? ADO.vaultK ?? 4;
  const n = opts.n ?? ADO.vaultN ?? 8;
  const dataShards = Math.max(1, Math.min(k, n));
  const parity = Math.max(0, n - dataShards);
  const chunk = Math.ceil(buf.length / dataShards) || 1;
  const pads = [];
  for (let i = 0; i < dataShards; i++) {
    const slice = buf.subarray(i * chunk, (i + 1) * chunk);
    const block = Buffer.alloc(chunk);
    slice.copy(block);
    pads.push(block);
  }
  let shares;
  try {
    const parityShares = parity > 0 ? rsEncode(pads, parity) : [];
    shares = pads.concat(parityShares);
  } catch {
    shares = pads.slice();
  }
  const digests = shares.map((s) => hex(sha256(s)).slice(0, 32));
  const tree = merkleTreeFromLeaves(digests);
  return {
    v: 1, k: dataShards, n: shares.length, chunk, merkleRoot: tree.root,
    digests, size: buf.length, blobDigest: hex(sha256(buf)).slice(0, 40),
    sharesB64: shares.map((s) => s.toString('base64')),
  };
}

function vaultSeal(blob, prev, opts = {}) {
  const plan = vaultShardPlan(blob, opts);
  const seq = (prev?.seq | 0) + 1;
  const entry = {
    v: 1, seq, prev: prev?.digest || null, merkleRoot: plan.merkleRoot,
    blobDigest: plan.blobDigest, k: plan.k, n: plan.n, digests: plan.digests,
  };
  entry.digest = hex(sha256(Buffer.from('vault:seal:v1:' + xiCanon(entry)))).slice(0, 40);
  return { ...entry, plan, ts: opts.ts || Date.now() };
}

function vaultOpen(shares, plan, opts = {}) {
  const need = plan?.k || opts.k || ADO.vaultK || 4;
  const n = plan?.n || (shares || []).length;
  const slot = new Array(n).fill(null);
  const raw = shares || [];
  for (let i = 0; i < raw.length && i < n; i++) {
    if (raw[i] == null) continue;
    slot[i] = Buffer.isBuffer(raw[i]) ? raw[i] : Buffer.from(String(raw[i]), 'base64');
  }
  const have = slot.filter(Boolean).length;
  if (have < need) return { ok: false, reason: 'need-more-shares', have, need };
  try {
    const decoded = rsDecode(need, slot, plan?.chunk || slot.find(Boolean)?.length || 0);
    if (!decoded) return { ok: false, reason: 'rs-null' };
    const out = Buffer.concat(decoded.map((p) => Buffer.isBuffer(p) ? p : Buffer.from(p)));
    const blob = plan?.size != null ? out.subarray(0, plan.size) : out;
    return { ok: true, blob, reason: 'rs-open', blobDigest: hex(sha256(blob)).slice(0, 40) };
  } catch (err) {
    return { ok: false, reason: err.message || 'rs-fail' };
  }
}

function antiRevisionGate(tip, chainTip, opts = {}) {
  if (!tip?.digest) return { ok: false, reason: 'no-tip' };
  if (!chainTip?.digest) return { ok: true, reason: 'genesis-ok' };
  if (opts.allowFork) return { ok: true, reason: 'fork-allowed' };
  if (tip.prev && tip.prev !== chainTip.digest && tip.prevDigest && tip.prevDigest !== chainTip.digest) {
    return { ok: false, reason: 'anti-revision-mismatch', expected: chainTip.digest, got: tip.prev || tip.prevDigest };
  }
  if (tip.prev && tip.prev !== chainTip.digest) {
    return { ok: false, reason: 'anti-revision-mismatch', expected: chainTip.digest, got: tip.prev };
  }
  return { ok: true, reason: 'hash-chain-ok' };
}

/** Sub-Mind + Interplanetary */
function subMindJobPlan(task, opts = {}) {
  const parts = Math.max(1, Math.min(64, opts.n || opts.parts || 4));
  const canon = typeof task === 'string' ? task : xiCanon(task);
  const root = hex(sha256(Buffer.from('submind:v1:' + canon))).slice(0, 40);
  const jobs = [];
  for (let i = 0; i < parts; i++) {
    jobs.push({
      i, digest: hex(sha256(Buffer.from(root + ':' + i))).slice(0, 32),
      root, payload: opts.split ? { part: i, of: parts, task } : { task, part: i, of: parts },
    });
  }
  return { v: 1, root, n: parts, jobs };
}

function assignSubMindWorkers(jobs, workers, opts = {}) {
  const now = opts.now || Date.now();
  const list = Object.entries(workers || {}).map(([id, w]) => ({ id, ...(w || {}) }))
    .filter((w) => w && (now - (w.ts || 0)) < (opts.ttlMs || WORKER_TTL_MS));
  const scored = list.map((w) => {
    const vw = (w.visitor || String(w.id || '').startsWith('v:'))
      ? visitorWeightGovPlan({ trust: w.trust, sla: w.sla, beatAgeMs: now - (w.ts || now) }).weight
      : 1;
    const pathPenalty = w.pathClass === 'satellite-path' ? 0.85 : (w.pathClass === 'regional' ? 0.95 : 1);
    return { ...w, score: (w.sla || 50) * vw * pathPenalty };
  }).sort((a, b) => b.score - a.score);
  const assigns = [];
  for (let i = 0; i < (jobs || []).length; i++) {
    const w = scored[i % Math.max(1, scored.length)] || null;
    assigns.push({ job: jobs[i], worker: w?.id || null, score: w?.score || 0 });
  }
  return { n: assigns.length, assigns, workers: scored.length };
}

function lightDelayContactPlan(opts = {}) {
  const preset = opts.preset || 'leo';
  const base = (ADO.lightPresets || {})[preset] ?? 80;
  const plan = buildOrbitalContactPlan({ now: opts.now || Date.now(), ...opts });
  const contacts = (plan.contacts || []).map((c) => ({
    ...c, propMs: Math.max(c.propMs || 0, base), lightPreset: preset,
  }));
  return { ...plan, contacts, lightPreset: preset, propFloorMs: base, ecMode: 'eventual-slow' };
}

/** S125 Dial Session Receipts */
function dialSessionReceiptPlan(dial, opts = {}) {
  if (!dial?.channel && !dial?.dial) return { ok: false, reason: 'no-dial' };
  const session = {
    v: 1,
    channel: dial.channel || dial.dial,
    mode: dial.mode || 'side-effect-node',
    handle: opts.handle || null,
    sideEffect: true,
    ts: opts.ts || Date.now(),
  };
  const sessionId = hex(sha256(Buffer.from(xiCanon(session)))).slice(0, 24);
  const body = makeReceiptBody(
    opts.dagId || ('dial:' + session.channel),
    { session: session.channel, mode: session.mode, sessionId },
    { opened: true, channel: session.channel, sessionId },
    { visitor: opts.visitor || null, ts: session.ts },
  );
  return { ok: true, session: { ...session, sessionId }, receipt: body, sessionId };
}

/** S127 Multi-Bridge Rendezvous */
function multiBridgeRendezvousPlan(bridges, opts = {}) {
  const list = (bridges || []).map((b, i) => {
    const o = typeof b === 'string' ? { url: b } : { ...b };
    const lag = o.lagMs ?? o.rttMs ?? (50 + i * 20);
    const pc = o.pathClass || networkPathClassFromProbe({ ok: true, rttMs: lag }).class;
    const score = 1 / (1 + lag / 100) * (pc === 'terrestrial' ? 1 : pc === 'regional' ? 0.85 : 0.65);
    return { ...o, lagMs: lag, pathClass: pc, score: +score.toFixed(4) };
  }).filter((b) => b.url).sort((a, b) => b.score - a.score);
  const best = list[0] || null;
  return { ok: !!best, rendezvous: best, bridges: list.slice(0, 16), n: list.length };
}

/** S128 Visitor Weight Governance */
function visitorWeightGovPlan(opts = {}) {
  const base = opts.base ?? ADO.visitorWeightBase ?? 0.35;
  const trust = Math.max(0, Math.min(1, opts.trust ?? 0.5));
  const sla = Math.max(0, Math.min(100, opts.sla ?? 50));
  const age = opts.beatAgeMs ?? 0;
  const fresh = age < 30_000 ? 1 : age < 90_000 ? 0.7 : 0.4;
  const weight = Math.max(0.05, Math.min(0.9, base * (0.5 + trust) * (0.5 + sla / 100) * fresh));
  return { weight: +weight.toFixed(4), base, trust, sla, fresh, beatAgeMs: age };
}

/** S129 Sticky Failover Probe Heal */
function stickyProbeHealPlan(stickyState, probes, opts = {}) {
  const need = opts.n ?? ADO.stickyGreenProbes ?? 3;
  const greens = (probes || []).filter((p) => p && p.ok);
  const streak = opts.streak != null ? opts.streak : greens.length;
  const sticky = !!(stickyState?.sticky || stickyState?.preferNsite);
  if (!sticky) return { sticky: false, release: false, reason: 'not-sticky', streak, need };
  if (streak >= need) return { sticky: false, release: true, reason: 'green-probe-quorum', streak, need };
  return { sticky: true, release: false, reason: 'await-green-probes', streak, need };
}

/** S130 Dual-Root Living Queue */
function dualRootLivingQueuePlan(trustState, queue, opts = {}) {
  const phase = trustState?.phase || null;
  const q = [...(queue || [])];
  if (phase === 'dual-root') {
    return { hold: true, drain: false, reason: 'dual-root-hold', queue: q, n: q.length, phase };
  }
  if (phase === 'cutover' || phase === 'single' || phase === 'active' || !phase) {
    return { hold: false, drain: q.length > 0, reason: 'cutover-drain', queue: q, n: q.length, phase: phase || 'open' };
  }
  return { hold: false, drain: false, reason: 'phase-' + phase, queue: q, n: q.length, phase };
}

/** S131 Prefetch→Distill Warm */
function prefetchDistillWarmPlan(prefetchResult, opts = {}) {
  if (!prefetchResult?.ok && !prefetchResult?.warm) {
    return { warm: false, reason: prefetchResult?.reason || 'prefetch-fail' };
  }
  return {
    warm: true, reason: 'prefetch-distill-warm',
    digest: prefetchResult.digest || opts.digest || null,
    seedFirst: true,
    distill: distillCdnPlan({
      digest: prefetchResult.digest || opts.digest,
      seedUrl: opts.seedUrl || prefetchResult.seedUrl,
      mirrors: opts.mirrors || prefetchResult.mirrors,
      agree: prefetchResult.agree ?? opts.agree,
    }),
  };
}

/** S132 Replica Lag Bound */
function replicaLagBoundGate(localTip, remoteTip, opts = {}) {
  const lag = replicaTipLagMs(localTip, remoteTip, opts);
  const bound = opts.boundMs ?? ADO.replicaLagBoundMs ?? 120_000;
  if (!lag.ok) return { ok: false, reason: 'replica-lag-bound', lagMs: lag.lagMs, bound };
  return { ok: true, reason: 'within-bound', lagMs: lag.lagMs, bound };
}

/** S133 Quote Market Clearing */
function quoteMarketClearingPlan(quotes, opts = {}) {
  const list = (quotes || []).filter((q) => q && (q.market || q.lagTax));
  if (!list.length) return { clear: false, reason: 'no-taxed-quotes', digest: null, n: 0 };
  const entries = list.map((q) => ({
    quoteId: q.id || q.quoteId, usd: q.usd ?? q.lagTax?.usd ?? q.price?.usd ?? 0,
    tier: q.tier || q.lagTax?.tier, ts: q.ts || opts.now || Date.now(),
  }));
  const digest = hex(sha256(Buffer.from('quote-clear:v1:' + xiCanon(entries)))).slice(0, 32);
  return { clear: true, reason: 'lag-tax-clearing', digest, n: entries.length, entries: entries.slice(0, 50), totalUsd: +entries.reduce((s, e) => s + (e.usd || 0), 0).toFixed(6) };
}

/** S134 Degraded→Full Auto Promote */
function degradedFullPromotePlan(opts = {}) {
  const workers = opts.workersAlive ?? 0;
  const mesh = !!opts.meshAlive;
  const orgOk = opts.organismOk !== false && (opts.organismQuorum?.ok !== false);
  if ((workers > 0 || mesh) && orgOk) {
    return { promote: true, profile: 'full', reason: 'mesh-restored', genome: 'healthy' };
  }
  return { promote: false, profile: opts.currentProfile || 'degraded', reason: 'still-thin' };
}

/** S135 Side-Effect WAL Ship */
function sideEffectWalShipPlan(pending, peers, opts = {}) {
  const n = (pending || []).length;
  const peerList = (peers || []).filter(Boolean);
  if (!n) return { ship: false, reason: 'empty-wal', n: 0, peers: peerList };
  const window = contactWindowShipGate(opts.dance, { heavy: true, force: opts.force });
  if (!window.ship) return { ship: false, reason: window.reason, n, peers: peerList, holdCustody: true };
  const digest = hex(sha256(Buffer.from('walship:v1:' + xiCanon(pending.slice(0, 20))))).slice(0, 32);
  return { ship: true, reason: 'wal-ship-peers', n, digest, peers: peerList.slice(0, 16), replay: !!opts.replay };
}

/** S136 Cryptographic App Continuum */
function cryptoAppContinuumManifest(parts = {}) {
  const core = {
    v: 1,
    capsule: parts.capsule || null,
    nsite: parts.nsite || parts.canon || null,
    dial: parts.dial || parts.dialChannel || null,
    merkle: parts.merkleRoot || parts.merkle || null,
    tip: parts.tipDigest || parts.tip || null,
    living: parts.living !== false,
  };
  const digest = hex(sha256(Buffer.from('crypto-app-continuum:v1:' + xiCanon(core)))).slice(0, 40);
  const ok = !!(core.merkle && core.tip && (core.nsite || core.dial));
  return { ...core, digest, ok, reason: ok ? 'continuum-sealed' : 'continuum-incomplete', ts: parts.ts || Date.now() };
}

function cryptoAppContinuumGate(manifest, opts = {}) {
  if (opts.allowIncomplete) return { ok: true, reason: 'skipped' };
  if (!manifest?.ok || !manifest?.digest) return { ok: false, reason: manifest?.reason || 'no-continuum' };
  if (opts.expectDigest && opts.expectDigest !== manifest.digest) {
    return { ok: false, reason: 'continuum-digest-mismatch' };
  }
  return { ok: true, reason: 'continuum-ok', digest: manifest.digest };
}

/** Sovereign (cryptographic citizenship — not a legal state) */
function sovereignPassportPlan(parts = {}) {
  const core = {
    v: 1,
    callingCard: parts.callingCard || null,
    genome: parts.genome || null,
    tip: parts.tipDigest || parts.tip || null,
    member: parts.memberDigest || parts.member || null,
    handle: parts.handle || null,
  };
  const digest = hex(sha256(Buffer.from('sovereign:passport:v1:' + xiCanon(core)))).slice(0, 40);
  return { ...core, digest, citizen: !!(core.callingCard && core.tip), ts: parts.ts || Date.now() };
}

function latticeLawGate(passport, trustState, opts = {}) {
  if (opts.allowAnon) return { ok: true, reason: 'anon-allowed' };
  if (!passport?.citizen && !passport?.digest) return { ok: false, reason: 'no-passport' };
  if (trustState?.phase === 'dual-root' && !opts.allowDual) {
    return { ok: false, reason: 'lattice-law-dual-root' };
  }
  if (opts.requireCard && !passport.callingCard) return { ok: false, reason: 'law-card-required' };
  return { ok: true, reason: 'lattice-law-ok', passport: passport.digest };
}

function organismCensusPlan(members, probes, opts = {}) {
  const now = opts.now || Date.now();
  const ttl = opts.ttlMs || 180_000;
  const list = Object.values(members || {}).filter((m) => m && (now - (m.ts || 0)) < ttl);
  const brains = list.filter((m) => m.brain || m.brainDigest);
  const visitors = list.filter((m) => m.visitor);
  const sat = (probes || []).filter((p) => p.satelliteLikely || p.pathClass === 'satellite-path' || p.class === 'satellite-path');
  return {
    nMembers: list.length, nBrains: brains.length, nVisitors: visitors.length,
    nSatellitePaths: sat.length, ts: now,
    note: 'sovereign census — cryptographic membership counts',
  };
}

// ═══════════ SIOS + ADIS Flock — Sovereign Internet OS / Organic AI Swarm ═══════════

/** Flock mind digest — מוח אחד לכל הלהקה */
function flockMindDigest(parts = {}) {
  const core = {
    v: 1,
    flockId: parts.flockId || null,
    tip: parts.tipDigest || parts.tip || null,
    brains: (parts.brainDigests || []).slice().sort(),
    kernel: parts.kernelDigest || null,
    vecRoot: parts.vecRoot || null,
  };
  return hex(sha256(Buffer.from('flock:mind:v1:' + xiCanon(core)))).slice(0, 40);
}

function flockMergePlan(members, opts = {}) {
  const list = Object.values(members || {}).filter((m) => m && m.mindDigest);
  const tallies = {};
  for (const m of list) tallies[m.mindDigest] = (tallies[m.mindDigest] || 0) + 1;
  const ranked = Object.entries(tallies).sort((a, b) => b[1] - a[1] || String(a[0]).localeCompare(b[0]));
  const winner = ranked[0] || null;
  const brainDigests = list.map((m) => m.brainDigest).filter(Boolean).sort();
  const merged = flockMindDigest({
    flockId: opts.flockId || list[0]?.flockId,
    tipDigest: opts.tipDigest || list[0]?.tipDigest,
    brainDigests,
    kernelDigest: opts.kernelDigest,
    vecRoot: opts.vecRoot,
  });
  return {
    ok: !!winner || brainDigests.length > 0,
    mindDigest: winner ? winner[0] : merged,
    votes: winner ? winner[1] : 0,
    nMembers: list.length,
    split: ranked.length > 1,
    mergedDigest: merged,
    reason: ranked.length > 1 ? 'quorum-merge' : 'unified',
  };
}

function flockAntennaReachPlan(probes, opts = {}) {
  const list = (probes || []).map((p) => ({ ...p, ...networkPathClassFromProbe(p) }));
  const okAntennas = list.filter((p) => p.ok);
  const sat = okAntennas.filter((p) => p.satelliteLikely || p.class === 'satellite-path');
  const minA = opts.minAntennas ?? ADO.minAntennas ?? 3;
  return {
    ok: okAntennas.length >= Math.min(minA, list.length || minA),
    nOk: okAntennas.length,
    nSatellitePaths: sat.length,
    reachable: okAntennas.slice(0, 16).map((p) => ({ url: p.url, kind: p.kind, class: p.class, rttMs: p.rttMs })),
    remoteConnect: okAntennas.length > 0,
    note: 'reach via public antennas; sat-path = ISP-class RTT, not bird control',
  };
}

function satellitePathConnectGate(probeOrClass, antennas, opts = {}) {
  const cls = typeof probeOrClass === 'string'
    ? { class: probeOrClass, satelliteLikely: probeOrClass === 'satellite-path', ok: true }
    : networkPathClassFromProbe(probeOrClass || {}, opts);
  const reach = flockAntennaReachPlan(antennas || [], opts);
  const satOk = cls.class === 'satellite-path' || cls.satelliteLikely;
  if (!reach.remoteConnect) return { ok: false, reason: 'no-antenna-reach', class: cls.class, reach };
  if (opts.requireSatellite && !satOk) return { ok: false, reason: 'not-satellite-path', class: cls.class, reach };
  return {
    ok: true,
    reason: satOk ? 'satellite-path-via-antenna' : 'terrestrial-via-antenna',
    class: cls.class,
    satelliteLikely: !!satOk,
    reach,
    connect: { mode: 'mesh-antenna', remote: true, control: false },
  };
}

/**
 * נוכחות בחלל — הכתובת הגלובלית + tip + שכפולים חיים גם על נתיבי space/satellite-path.
 * כנה: מתחברים לאנטנות/ממסרי space ורוכבים על ISP לווייני — לא שולטים בלוויין פיזי.
 */
function spacePresencePlan(parts = {}) {
  const spaceRelays = parts.spaceClass || SPACE_MESH.spaceClass || [];
  const probes = parts.probes || [];
  const satPaths = probes.filter((p) => {
    const c = networkPathClassFromProbe(p);
    return c.satelliteLikely || c.class === 'satellite-path';
  });
  const canon = parts.canon || null;
  const tip = parts.tipDigest || parts.tip || null;
  const flockId = parts.flockId || null;
  const core = {
    v: 1,
    connectNotControl: true,
    addressInSpace: parts.addressInSpace !== false && (SPACE_MESH.addressInSpace !== false),
    canon,
    tip,
    flockId,
    spaceAntennas: spaceRelays.slice(0, 16),
    nSpaceAntennas: spaceRelays.length,
    nSatellitePaths: satPaths.length,
    living: parts.living !== false,
    immortal: parts.immortal !== false,
  };
  const digest = hex(sha256(Buffer.from('space:presence:v1:' + xiCanon({
    canon: core.canon, tip: core.tip, flockId: core.flockId, n: core.nSpaceAntennas,
  })))).slice(0, 40);
  const ok = !!(core.addressInSpace && core.nSpaceAntennas > 0 && (core.canon || core.tip));
  return {
    ...core,
    digest,
    ok,
    reason: ok ? 'global-address-lives-in-space-paths' : 'space-presence-incomplete',
    note: 'Lives on space-class antennas + satellite-path ISP; does not command physical satellites',
    ts: parts.ts || Date.now(),
  };
}

function spaceAddressPinGate(presence, opts = {}) {
  if (!presence) return { ok: false, reason: 'no-presence' };
  if (!presence.connectNotControl) return { ok: false, reason: 'must-connect-not-control' };
  if (!presence.addressInSpace) return { ok: false, reason: 'address-not-in-space' };
  if (!(presence.nSpaceAntennas > 0) && !opts.allowEmpty) {
    return { ok: false, reason: 'no-space-antennas' };
  }
  if (!presence.canon && !presence.tip && !opts.allowUntipped) {
    return { ok: false, reason: 'no-canon-or-tip' };
  }
  return { ok: true, reason: 'space-address-pinned', digest: presence.digest };
}

/**
 * מזהה לוויין/צומת־חלל לוגי — יציב מ־URL/slot (לא NORAD ID פיזי).
 * דרך המזהה גולשים, משכפלים, ומגלים שכנים דרך אותו צומת.
 */
function spaceNodeIdFromEndpoint(endpoint, opts = {}) {
  const url = typeof endpoint === 'string' ? endpoint : (endpoint?.url || '');
  const slot = endpoint?.slot ?? opts.slot ?? null;
  const segment = endpoint?.segment || (opts.space ? 'space' : 'antenna');
  const pathClass = endpoint?.pathClass || endpoint?.class || opts.pathClass || null;
  const core = { v: 1, url, slot, segment, pathClass };
  const id = 'sat:' + hex(sha256(Buffer.from('space:node:v1:' + xiCanon(core)))).slice(0, 20);
  return { id, url: url || null, slot, segment, pathClass, control: false, connect: true };
}

/** קטלוג צמתי־חלל מזוהים מ־dance + spaceClass + probes */
function discoverSpaceCatalog(opts = {}) {
  const nodes = [];
  const seen = new Set();
  const push = (ep) => {
    const n = spaceNodeIdFromEndpoint(ep, opts);
    if (!n.id || seen.has(n.id)) return;
    if (!n.url && n.slot == null) return;
    seen.add(n.id);
    nodes.push({ ...n, ok: ep.ok !== false, rttMs: ep.rttMs ?? null, elev: ep.elev ?? null, score: ep.score ?? null });
  };
  let spaceIdx = 0;
  for (const u of (opts.spaceClass || SPACE_MESH.spaceClass || [])) {
    const slot = opts.slotOf?.(u) ?? spaceIdx++;
    push({ url: u, segment: 'space', slot });
  }
  for (const a of (opts.dance?.active || opts.dance?.ranked || [])) {
    if (a.segment === 'space' || a.url) push(a);
  }
  for (const p of (opts.probes || [])) {
    const cls = networkPathClassFromProbe(p);
    if (cls.satelliteLikely || cls.class === 'satellite-path' || p.kind === 'space-class') {
      push({ ...p, segment: 'space', pathClass: cls.class });
    }
  }
  // slots לוגיים גלויים — מזהים גם בלי URL (מסלול לוגי)
  for (const s of (opts.dance?.visibleSlots || [])) {
    push({ url: null, segment: 'space', slot: s, pathClass: 'orbital-slot' });
  }
  nodes.sort((a, b) => String(a.id).localeCompare(b.id));
  return {
    v: 1, n: nodes.length, nodes: nodes.slice(0, opts.limit || 64),
    connectNotControl: true,
    note: 'logical sat IDs from antennas/slots — hop+replicate; not NORAD bird control',
  };
}

/** מהצומת הנוכחי — שכנים דרך CGR/contact plan (לווין→לווין) */
function spaceNeighborGossipPlan(fromNodeId, catalog, contactPlan, opts = {}) {
  const nodes = catalog?.nodes || [];
  const byId = Object.fromEntries(nodes.map((n) => [n.id, n]));
  const from = byId[fromNodeId] || nodes.find((n) => n.id === fromNodeId);
  if (!from) return { ok: false, reason: 'unknown-from', from: fromNodeId, neighbors: [] };
  const fromKey = from.slot != null ? ('orb:' + from.slot) : null;
  const neighbors = [];
  const contacts = contactPlan?.contacts || [];
  if (fromKey) {
    for (const c of contacts) {
      if (c.from !== fromKey) continue;
      const m = String(c.to || '').match(/^orb:(\d+)/);
      if (!m) continue;
      const slot = Number(m[1]);
      const peer = nodes.find((n) => n.slot === slot) || spaceNodeIdFromEndpoint({ segment: 'space', slot });
      neighbors.push({
        id: peer.id, slot, via: from.id, elev: c.elev ?? null,
        kind: c.kind || 'crosslink', delayHintMs: (c.end || 0) - (c.start || 0),
      });
    }
  }
  // נפילה: שכנים לפי קרבת slot / רשימת הקטלוג (רשת+חלל)
  if (!neighbors.length) {
    for (const n of nodes) {
      if (n.id === from.id) continue;
      const nearSlot = from.slot != null && n.slot != null
        && Math.abs(n.slot - from.slot) <= (opts.slotRadius || 2);
      const catalogPeer = from.slot == null || n.slot == null;
      if (nearSlot || catalogPeer) {
        neighbors.push({ id: n.id, slot: n.slot, via: from.id, kind: nearSlot ? 'catalog-adjacent' : 'catalog-mesh', url: n.url });
      }
    }
  }
  // ייחודיות
  const uniq = [];
  const seen = new Set();
  for (const n of neighbors) {
    if (seen.has(n.id)) continue;
    seen.add(n.id);
    uniq.push(n);
  }
  return {
    ok: true, from: from.id, neighbors: uniq.slice(0, opts.maxNeighbors || 12),
    n: uniq.length, connectNotControl: true,
  };
}

/**
 * שרשרת שכפול hop-by-hop: ground → sat A → sat B → … (BFS על שכנים)
 * כל hop = מזהה לוויין לוגי שאליו משכפלים tip/כתובת.
 */
function spaceHopReplicationPlan(catalog, opts = {}) {
  const nodes = catalog?.nodes || [];
  if (!nodes.length) return { ok: false, reason: 'empty-catalog', hops: [], chain: [] };
  const contactPlan = opts.contactPlan || buildOrbitalContactPlan({ now: opts.now || Date.now() });
  // העדף התחלה עם slot / שכנים — כדי שבאמת תיבנה שרשרת sat→sat
  let startId = opts.startId || null;
  if (!startId) {
    const withSlot = nodes.find((n) => n.slot != null);
    const withNeigh = nodes.find((n) => (spaceNeighborGossipPlan(n.id, catalog, contactPlan, opts).neighbors || []).length > 0);
    startId = (withNeigh || withSlot || nodes[0]).id;
  }
  const maxHops = opts.maxHops || 8;
  const tip = opts.tipDigest || opts.tip || null;
  const visited = new Set();
  const chain = [];
  let cur = startId;
  for (let h = 0; h < maxHops && cur; h++) {
    if (visited.has(cur)) break;
    visited.add(cur);
    const node = nodes.find((n) => n.id === cur) || { id: cur };
    chain.push({
      hop: h, id: node.id, url: node.url || null, slot: node.slot ?? null,
      tip, action: 'replicate', connectNotControl: true,
    });
    const gossip = spaceNeighborGossipPlan(cur, catalog, contactPlan, opts);
    const cand = (gossip.neighbors || []).filter((n) => !visited.has(n.id));
    // העדף שכן עם slot/crosslink (לוויין→לוויין) לפני catalog-mesh כללי
    const next = cand.find((n) => n.slot != null && (n.kind === 'crosslink' || n.kind === 'catalog-adjacent'))
      || cand.find((n) => n.slot != null)
      || cand[0];
    if (!next) break;
    cur = next.id;
  }
  return {
    ok: chain.length > 0,
    reason: chain.length > 1 ? 'multi-hop-space-replicate' : 'single-hop',
    chain,
    hops: chain.length,
    visitedIds: [...visited],
    tip,
    note: 'discover sat ID → replicate → gossip next sat via same path → repeat across network/space',
  };
}

/** S137 Address-on-Hop receipt — כל hop חותם receipt (tip+satId) ל־skylive */
function addressOnHopReceiptPlan(hop, opts = {}) {
  if (!hop?.id) return { ok: false, reason: 'no-hop' };
  const tip = hop.tip || opts.tipDigest || opts.tip || null;
  const inputs = {
    satId: hop.id, hop: hop.hop ?? 0, tip, slot: hop.slot ?? null,
    canon: opts.canon || null, url: hop.url || null,
  };
  const output = {
    action: hop.action || 'replicate', connectNotControl: true,
    skylivePin: true, addressOnHop: true,
  };
  const receipt = makeReceiptBody(
    opts.dagId || ('space-hop:' + hop.id + ':' + (hop.hop ?? 0)),
    inputs, output,
    { visitor: opts.visitor || null, ts: opts.ts || Date.now() },
  );
  return {
    ok: true, satId: hop.id, hop: hop.hop ?? 0, tip, receipt,
    reason: 'address-on-hop-receipt', verify: verifyReceiptBody(receipt, output),
  };
}

function addressOnHopReceiptChain(hopPlan, opts = {}) {
  const chain = hopPlan?.chain || [];
  if (!chain.length) return { ok: false, reason: 'empty-chain', receipts: [] };
  const receipts = chain.map((h) => addressOnHopReceiptPlan(
    { ...h, tip: h.tip || hopPlan.tip || opts.tip },
    opts,
  ));
  const allOk = receipts.every((r) => r.ok && r.verify);
  const digest = hex(sha256(Buffer.from('s137:chain:' + xiCanon(receipts.map((r) => r.receipt?.id))))).slice(0, 32);
  return {
    ok: allOk, n: receipts.length, receipts, digest,
    reason: allOk ? 'skylive-hop-receipts' : 'partial-hop-receipts',
    skylive: { hopReceipts: receipts.map((r) => ({ satId: r.satId, hop: r.hop, id: r.receipt?.id, tip: r.tip })) },
  };
}

/** S138 Catalog quorum — ≥2 peers מאשרים אותו sat: לפני שכפול */
function spaceCatalogQuorumGate(satId, peerVotes, opts = {}) {
  const id = String(satId || '');
  if (!id.startsWith('sat:')) return { ok: false, reason: 'bad-sat-id', satId: id };
  const min = opts.minPeers ?? SPACE_MESH.catalogQuorum ?? 2;
  const votes = (peerVotes || []).filter((v) => v && (v.satId === id || v.id === id));
  const peers = [...new Set(votes.map((v) => String(v.peerId || v.peer || v.from || '')))].filter(Boolean);
  if (peers.length < min) {
    return { ok: false, reason: 'catalog-quorum-thin', satId: id, peers: peers.length, need: min, agree: votes.length };
  }
  return {
    ok: true, reason: 'catalog-quorum-ok', satId: id, peers: peers.length, need: min,
    agree: votes.length, peerIds: peers.slice(0, 16),
  };
}

function spaceCatalogQuorumBatch(hopPlan, votesBySat, opts = {}) {
  const chain = hopPlan?.chain || [];
  const gates = chain.map((h) => spaceCatalogQuorumGate(h.id, votesBySat?.[h.id] || votesBySat?.[h.id?.slice?.(0, 12)] || [], opts));
  const blocked = gates.filter((g) => !g.ok);
  const allowed = gates.filter((g) => g.ok).map((g) => g.satId);
  return {
    ok: blocked.length === 0 && gates.length > 0,
    reason: blocked.length ? 'quorum-blocked' : 'quorum-all-ok',
    gates, allowed, blocked: blocked.map((b) => b.satId),
  };
}

/** S139 Hop×Vault — shard vault על N hops שונים (anti-extinction בחלל) */
function hopVaultShardPlan(blob, hopPlan, opts = {}) {
  const ids = [...new Set((hopPlan?.chain || hopPlan?.visitedIds || []).map((h) => (typeof h === 'string' ? h : h.id)).filter(Boolean))];
  const needHops = opts.minHops ?? 3;
  if (ids.length < needHops) {
    return { ok: false, reason: 'need-more-hops', have: ids.length, need: needHops, placement: [] };
  }
  const placeOn = ids.slice(0, opts.n ?? Math.max(needHops, opts.n || needHops));
  const n = placeOn.length;
  const k = Math.min(opts.k ?? Math.max(2, Math.ceil(n * 0.5)), n);
  const sealed = vaultSeal(blob, opts.prev || null, { k, n, ts: opts.ts });
  const placement = placeOn.map((satId, i) => ({
    satId, shareIndex: i, digest: sealed.digests?.[i] || null,
    shareB64: sealed.plan?.sharesB64?.[i] || null,
  }));
  return {
    ok: true, reason: 'hop-vault-sharded', k, n, merkleRoot: sealed.merkleRoot,
    blobDigest: sealed.blobDigest, digest: sealed.digest, placement, plan: sealed.plan, seq: sealed.seq,
    antiExtinction: true, connectNotControl: true,
  };
}

function hopVaultOpenPlan(hopVault, availableBySat, opts = {}) {
  if (!hopVault?.ok && !hopVault?.plan) return { ok: false, reason: 'no-hop-vault' };
  const plan = hopVault.plan || hopVault;
  const shares = new Array(plan.n || (hopVault.placement || []).length).fill(null);
  for (const p of (hopVault.placement || [])) {
    const got = availableBySat?.[p.satId];
    if (got != null) shares[p.shareIndex] = got;
    else if (p.shareB64 && opts.useLocal) shares[p.shareIndex] = p.shareB64;
  }
  return { ...vaultOpen(shares, plan, opts), placement: hopVault.placement, reason: 'hop-vault-open' };
}

/** S140 ADIS recall על hop — זיכרון רק מצמתי space שיש להם replica */
function adisRecallOnHopPlan(query, store, replicas, opts = {}) {
  const replicaIds = new Set(
    Object.keys(replicas || {}).concat((opts.replicaIds || []).map(String)).filter((id) => id.startsWith('sat:')),
  );
  if (!replicaIds.size && opts.requireReplica !== false) {
    return { ok: false, reason: 'no-space-replicas', recall: null, filtered: 0 };
  }
  const filtered = {};
  for (const [k, v] of Object.entries(store || {})) {
    const sat = v?.meta?.satId || v?.satId || v?.meta?.hopSat || null;
    if (sat && replicaIds.has(sat)) filtered[k] = v;
    else if (!sat && opts.allowUntagged) filtered[k] = v;
  }
  if (!Object.keys(filtered).length) {
    return { ok: false, reason: 'no-hop-memory', recall: null, filtered: 0, replicaN: replicaIds.size };
  }
  const recall = adisRecallPlan(query, filtered, opts);
  return {
    ok: recall.ok, recall, filtered: Object.keys(filtered).length,
    replicaN: replicaIds.size, reason: 'adis-recall-on-hop',
  };
}

/** S141 SIOS credit על hop — economy מחייבת bandwidth לכל crosslink */
function hopBandwidthDebitPlan(edge, opts = {}) {
  const from = edge?.from || edge?.fromId || null;
  const to = edge?.to || edge?.toId || null;
  if (!from || !to) return { ok: false, reason: 'no-edge', debit: 0 };
  const bytes = Math.max(0, edge?.bandwidthBytes ?? edge?.bytes ?? opts.bytes ?? 64_000);
  const rate = opts.creditPerMb ?? SIOS.creditPerMb ?? 0.0001;
  const debit = +((bytes / 1e6) * rate).toFixed(6);
  return {
    ok: true, from, to, bytes, debit, kind: edge?.kind || 'crosslink',
    digest: hex(sha256(Buffer.from('s141:bw:' + from + ':' + to + ':' + debit))).slice(0, 24),
  };
}

function hopCrosslinkCreditGate(edge, hostCredit, opts = {}) {
  const debitPlan = hopBandwidthDebitPlan(edge, opts);
  if (!debitPlan.ok) return { ok: false, reason: debitPlan.reason, debitPlan };
  const settle = economySettleGate(hostCredit, { debit: debitPlan.debit }, opts);
  return {
    ok: settle.ok, reason: settle.ok ? 'hop-crosslink-credited' : settle.reason,
    debitPlan, settle, from: debitPlan.from, to: debitPlan.to,
  };
}

function hopChainCreditGate(hopPlan, hostCredit, opts = {}) {
  const chain = hopPlan?.chain || [];
  const edges = [];
  for (let i = 0; i < chain.length - 1; i++) {
    edges.push({
      from: chain[i].id, to: chain[i + 1].id, kind: 'crosslink',
      bandwidthBytes: opts.bytesPerHop ?? 64_000,
    });
  }
  let credit = hostCredit?.credit ?? hostCredit ?? 0;
  const gates = [];
  for (const e of edges) {
    const g = hopCrosslinkCreditGate(e, { credit }, opts);
    gates.push(g);
    if (!g.ok) return { ok: false, reason: 'hop-credit-fail', gates, at: e, balance: credit };
    credit = g.settle.balance;
  }
  return { ok: true, reason: 'hop-chain-credited', gates, nEdges: edges.length, balance: credit };
}

/**
 * Hop × Immortal × Living Address — כתובת גלובלית שנודדת בין sat IDs
 * (לא תלויה במחשב מקור אחד).
 */
function livingAddressRoamSatPlan(presence, hopPlan, opts = {}) {
  const canon = presence?.canon || opts.canon || null;
  const tip = presence?.tip || hopPlan?.tip || opts.tip || null;
  const ids = hopPlan?.visitedIds || (hopPlan?.chain || []).map((h) => h.id).filter(Boolean);
  if (!canon && !tip) return { ok: false, reason: 'no-address', roosts: [] };
  if (!ids.length) return { ok: false, reason: 'no-sat-roosts', roosts: [] };
  const roosts = ids.map((satId, i) => ({
    satId, tip, canon, hop: i, living: true, immortal: true,
    home: i === 0, digest: hex(sha256(Buffer.from('roam:' + satId + ':' + (tip || '') + ':' + (canon || '')))).slice(0, 24),
  }));
  const roamDigest = hex(sha256(Buffer.from('roam-addr:' + xiCanon(roosts.map((r) => r.digest))))).slice(0, 32);
  return {
    ok: true, reason: 'address-roams-sats', roosts, n: roosts.length, tip, canon, roamDigest,
    originOptional: true, connectNotControl: true,
  };
}

/**
 * ADIS flock × Space hop × Sub-Mind —
 * משימות קלות על האנטנה הקרובה; כבדות על workers; זיכרון מתפזר בשרשרת.
 */
function flockHopSubMindPlan(task, hopPlan, workers, opts = {}) {
  const jobs = subMindJobPlan(task, { n: opts.parts || Math.max(2, Math.min(8, (hopPlan?.hops || 2) + 1)) });
  const light = [];
  const heavy = [];
  const sats = hopPlan?.chain || [];
  for (const job of jobs.jobs) {
    const isLight = (job.i % 2 === 0) || opts.allLight;
    if (isLight && sats.length) {
      const sat = sats[job.i % sats.length];
      light.push({ ...job, class: 'light', satId: sat.id, pathClass: 'satellite-path' });
    } else {
      heavy.push({ ...job, class: 'heavy', pathClass: 'mesh-worker' });
    }
  }
  const assign = assignSubMindWorkers(heavy.length ? heavy : jobs.jobs, workers || {}, opts);
  const memoryPins = sats.map((s) => ({ satId: s.id, tip: hopPlan?.tip || null, memory: true }));
  return {
    ok: true, root: jobs.root, light, heavy: assign.assigns || [], memoryPins,
    nLight: light.length, nHeavy: (assign.assigns || []).length || heavy.length,
    reason: 'flock-hop-submind', workers: assign.workers || 0,
  };
}

/**
 * Vault RS × CGR dark windows × WAL —
 * custody מחזיק בחושך; heal+WAL ship כשחלון נפתח.
 */
function vaultDarkWindowHealPlan(bundle, dance, walPending, peers, opts = {}) {
  const custody = dtnCustodyGate(bundle, opts.now || Date.now());
  const window = contactWindowShipGate(dance, { heavy: true, force: opts.force, contactOpen: opts.contactOpen });
  const wal = sideEffectWalShipPlan(walPending || [], peers || [], { ...opts, dance });
  if (!custody.ok && custody.expired) {
    return { ok: false, reason: 'custody-expired', custody, window, wal, heal: false, ship: false };
  }
  if (!window.ship) {
    return {
      ok: true, reason: 'dark-hold-custody', custody, window, wal,
      heal: false, ship: false, hold: true,
    };
  }
  return {
    ok: true, reason: 'contact-open-heal-wal', custody, window, wal,
    heal: true, ship: !!(wal?.ship),
    hold: false,
  };
}

/** DID + SkyDrive + Hop catalog — זהות+קבצים מעוגנים ב־sat: IDs */
function didSkyDriveHopPinPlan(did, pathStr, hopCatalogOrPlan, opts = {}) {
  const doc = typeof did === 'string' ? didDocumentPlan({ id: did, ...opts }) : (did?.id ? did : didDocumentPlan(did || {}));
  const ids = (hopCatalogOrPlan?.visitedIds
    || (hopCatalogOrPlan?.chain || []).map((h) => h.id)
    || (hopCatalogOrPlan?.nodes || []).map((n) => n.id)
    || []).filter(Boolean);
  if (!ids.length) return { ok: false, reason: 'no-sat-pins', did: doc.id };
  const put = skyDrivePutPlan(pathStr, opts.blob ?? opts.data ?? ('sky:' + doc.id + ':' + pathStr), {
    k: opts.k ?? Math.min(2, ids.length), n: Math.min(opts.n ?? ids.length, ids.length), prev: opts.prev,
  });
  if (!put.ok) return put;
  const pins = ids.slice(0, put.plan?.n || ids.length).map((satId, i) => ({
    satId, shareIndex: i, path: put.path, digest: put.digest, did: doc.id,
  }));
  return {
    ok: true, did: doc, path: put.path, digest: put.digest, merkleRoot: put.merkleRoot,
    pins, put, reason: 'did-skydrive-hop-pinned', dfsOnOrbit: true,
  };
}

/**
 * Ξ DAG × satellite-path gate × economy —
 * חישוב טהור רץ רק כשיש מסלול/קרדיט (בלי LLM).
 */
function xiSatEconomyGate(dag, probes, hostCredit, opts = {}) {
  let order;
  try { order = xiDagPlan(dag); }
  catch (err) { return { ok: false, reason: 'xi-dag-invalid', error: err.message }; }
  const sat = satellitePathConnectGate(
    probes?.find?.((p) => p.satelliteLikely || p.pathClass === 'satellite-path') || probes?.[0] || {},
    Array.isArray(probes) ? probes : (probes?.antennas || []),
    { requireSatellite: opts.requireSatellite !== false },
  );
  if (!sat.ok && opts.requireSatellite !== false) {
    return { ok: false, reason: 'no-satellite-path', sat, order, run: false };
  }
  const estBytes = opts.bytes ?? (order.length * 8_000);
  const debit = hopBandwidthDebitPlan({
    from: 'xi:local', to: 'xi:mesh', bandwidthBytes: estBytes, kind: 'xi-dag',
  }, opts);
  const settle = economySettleGate(hostCredit, { debit: debit.debit }, opts);
  if (!settle.ok) return { ok: false, reason: 'xi-insufficient-credit', sat, settle, debit, order, run: false };
  const dagId = xiDagId(dag);
  return {
    ok: true, reason: 'xi-sat-economy-ready', run: true, order, dagId, sat, settle, debit,
    llm: false, pureCompute: true,
  };
}

/** S142 Roam→Skylive Autopublish — כש־roamDigest משתנה, heal מפרסם skylive מחדש */
function roamSkyliveAutopublishGate(prevRoam, nextRoam, opts = {}) {
  if (!nextRoam?.roamDigest) return { republish: false, reason: 'no-roam' };
  if (opts.force) return { republish: true, reason: 'forced', digest: nextRoam.roamDigest };
  if (!prevRoam?.roamDigest) return { republish: true, reason: 'first-roam', digest: nextRoam.roamDigest };
  if (prevRoam.roamDigest === nextRoam.roamDigest) {
    return { republish: false, reason: 'roam-unchanged', digest: nextRoam.roamDigest };
  }
  return {
    republish: true, reason: 'roamDigest-changed',
    prev: prevRoam.roamDigest, next: nextRoam.roamDigest, digest: nextRoam.roamDigest,
  };
}

/** S143 Quorum Tip Cutover — tip חדש רק אחרי S138 על ≥k roosts */
function quorumTipCutoverGate(newTip, roosts, votesBySat, opts = {}) {
  const tip = typeof newTip === 'string' ? newTip : (newTip?.digest || newTip?.tip || null);
  if (!tip) return { ok: false, cutover: false, reason: 'no-tip' };
  const minK = opts.minRoosts ?? opts.k ?? SPACE_MESH.catalogQuorum ?? 2;
  const list = Array.isArray(roosts) ? roosts : (roosts?.roosts || []);
  const satIds = [...new Set(list.map((r) => r.satId || r.id).filter(Boolean))];
  if (satIds.length < minK) {
    return { ok: false, cutover: false, reason: 'thin-roosts', have: satIds.length, need: minK, tip };
  }
  const gates = satIds.slice(0, opts.maxCheck || 16).map((id) =>
    spaceCatalogQuorumGate(id, votesBySat?.[id] || [], opts));
  const okN = gates.filter((g) => g.ok).length;
  if (okN < minK) {
    return { ok: false, cutover: false, reason: 'quorum-roosts-thin', okN, need: minK, tip, gates };
  }
  return { ok: true, cutover: true, reason: 'quorum-tip-cutover', tip, okN, need: minK, satIds: satIds.slice(0, okN) };
}

/** S144 Hop-Vault Anti-Revision — antiRevisionGate על digest של placement */
function hopVaultAntiRevisionGate(hopVault, chainTip, opts = {}) {
  if (!hopVault?.digest && !hopVault?.merkleRoot) return { ok: false, reason: 'no-hop-vault' };
  const tip = {
    digest: hopVault.digest || hopVault.merkleRoot,
    prev: hopVault.prev || hopVault.prevDigest || null,
  };
  const gate = antiRevisionGate(tip, chainTip || null, opts);
  return {
    ...gate,
    hopVaultDigest: tip.digest,
    merkleRoot: hopVault.merkleRoot || null,
    placementN: (hopVault.placement || []).length,
    reason: gate.ok ? (gate.reason === 'genesis-ok' ? 'hop-vault-genesis' : 'hop-vault-anti-revision-ok') : gate.reason,
  };
}

/** S145 Hop Rain Stream — אירועי hop/receipt ל־#adis-rain */
function hopRainStreamPlan(events, opts = {}) {
  const raw = (events || []).slice(0, opts.limit || 64);
  const traced = raw.map((ev) => adisTracePlan({
    type: ev.type || 'hop',
    msg: ev.msg || `${ev.satId || ev.id || ''} tip=${String(ev.tip || '').slice(0, 8)} rc=${String(ev.receiptId || ev.id || '').slice(0, 8)}`,
    flockId: ev.flockId || opts.flockId || null,
    pathClass: ev.pathClass || 'satellite-path',
    ts: ev.ts,
  }, opts));
  return {
    ok: true, n: traced.length, events: traced,
    lines: traced.map((t) => t.line),
    digest: hex(sha256(Buffer.from('hop-rain:' + xiCanon(traced.map((t) => t.digest))))).slice(0, 24),
    reason: 'hop-rain-stream',
  };
}

/** S146 Crosslink Bandwidth Market — quotes של S122 על חיוב S141 */
function crosslinkBandwidthMarketPlan(debitPlan, opts = {}) {
  if (!debitPlan || (!(debitPlan.debit > 0) && !debitPlan.ok)) {
    return { market: false, reason: 'no-debit' };
  }
  const mult = opts.mult ?? 1.25;
  const base = +(debitPlan.debit || 0);
  const taxed = +(base * mult).toFixed(6);
  const quote = {
    id: opts.quoteId || ('xl:' + (debitPlan.digest || `${debitPlan.from}:${debitPlan.to}`)),
    price: { usd: base },
    lagTax: { tier: 'crosslink', mult, usd: taxed },
  };
  const market = lagTaxQuoteMarketPlan(quote, { alwaysMarket: opts.alwaysMarket !== false });
  return {
    market: !!market.market,
    reason: market.market ? 'crosslink-bandwidth-market' : (market.reason || 'no-market'),
    quoteId: quote.id, usd: taxed, baseUsd: base, tier: 'crosslink',
    from: debitPlan.from, to: debitPlan.to, quote, lagTaxMarket: market,
  };
}

/** S147 Ξ Receipt-on-Sat — תוצאת DAG ננעלת ל־sat: כמו S137 */
function xiReceiptOnSatPlan(xiGate, satId, opts = {}) {
  if (!xiGate?.ok || !xiGate.run) return { ok: false, reason: xiGate?.reason || 'xi-not-ready' };
  const id = String(satId || '');
  if (!id.startsWith('sat:')) return { ok: false, reason: 'bad-sat-id', satId: id };
  const output = {
    dagId: xiGate.dagId, result: opts.result ?? true, pureCompute: true, llm: false, satId: id,
  };
  const receipt = makeReceiptBody(
    opts.dagId || ('xi-sat:' + id + ':' + xiGate.dagId),
    { satId: id, dagId: xiGate.dagId, order: xiGate.order || [] },
    output,
    { ts: opts.ts || Date.now(), visitor: opts.visitor || null },
  );
  return {
    ok: true, satId: id, dagId: xiGate.dagId, receipt,
    verify: verifyReceiptBody(receipt, output), reason: 'xi-receipt-on-sat',
  };
}

/** S148 Dual-Root Hop Freeze — S117 חוסם גם hop-vault ship */
function dualRootHopFreezeGate(trustState, hopVaultOrShip, opts = {}) {
  const freeze = skyliveDualRootFreezeGate(trustState, opts);
  if (!freeze.ok) {
    return {
      ok: false, ship: false, reason: 'dual-root-hop-freeze', freeze,
      hopVault: hopVaultOrShip?.digest || hopVaultOrShip?.merkleRoot || null,
    };
  }
  return {
    ok: true, ship: true, reason: 'hop-vault-ship-allowed', freeze,
    hopVault: hopVaultOrShip?.digest || hopVaultOrShip?.merkleRoot || null,
  };
}

/** S149 Flock Merge on Roosts — merge רק בין replicas עם quorum על roost */
function flockMergeOnRoostsPlan(members, roosts, votesBySat, opts = {}) {
  const roostIds = new Set(
    (Array.isArray(roosts) ? roosts : (roosts?.roosts || [])).map((r) => r.satId || r.id).filter(Boolean),
  );
  const filtered = {};
  for (const [id, m] of Object.entries(members || {})) {
    const sat = m.satId || m.roostSat || m.hopSat || null;
    if (roostIds.size && sat && !roostIds.has(sat)) continue;
    if (sat && opts.requireQuorum !== false) {
      const votes = votesBySat?.[sat] || m.votes || [];
      const q = spaceCatalogQuorumGate(sat, votes, opts);
      if (!q.ok) continue;
    }
    if (roostIds.size && !sat && opts.requireSat !== false) continue;
    filtered[id] = m;
  }
  if (!Object.keys(filtered).length) {
    return { ok: false, reason: 'no-quorum-roost-members', merge: null, nFiltered: 0 };
  }
  const merge = flockMergePlan(filtered, opts);
  return {
    ok: !!merge.ok, merge, nFiltered: Object.keys(filtered).length,
    roostN: roostIds.size, reason: 'flock-merge-on-roosts',
  };
}

/** S150 Dark-Window Prefetch Cap — S106+S118 בזמן hold:true */
function darkWindowPrefetchCapPlan(darkHeal, stats, cfg, genome, pendingN, opts = {}) {
  if (darkHeal?.hold) {
    return {
      ok: false, allow: 0, hold: true, reason: 'dark-window-prefetch-cap',
      mode: 'custody-hold', budget: null, receiptCap: null,
    };
  }
  const budget = budgetPrefetchGate(stats || {}, cfg || {}, genome || 'full', pendingN || 0, opts);
  const receiptCap = prefetchReceiptCapPlan(stats || {}, cfg || {}, pendingN || 0, opts);
  if (!budget.ok) {
    return { ok: false, allow: 0, hold: false, reason: budget.reason, budget, receiptCap };
  }
  if (!receiptCap.ok) {
    return { ok: false, allow: 0, hold: false, reason: receiptCap.reason, budget, receiptCap };
  }
  return {
    ok: true, allow: budget.allow, hold: false, reason: 'prefetch-open',
    budget, receiptCap, mode: budget.mode,
  };
}

// ═══════════ SkyLattice Wave S151–S172 — BFT → OS חי (חינוכי / מחובר) ═══════════
// כנות: PBFT-lite, Lattice-KEM, Σ-ZK, toy FHE — אלגוריתמים מצומצמים לימודיים
// מחוברים לפרוטוקול. לא תחליף ל־ML-KEM/Dilithium/libsnark/NIST מאושרים.
// WebRTC/P2P: מכונת־מצבים + signaling; DataChannel אמיתי תלוי סביבה.
// DTN transports: store-and-forward לוגי — לא LoRa/Bluetooth חומרה בלי מודם.

const LATTICE_Q = 3329; // מודולוס לימודי קטן (לא Kyber אמיתי)
const LATTICE_N = 8;
const LATTICE_NOISE = 2;

/** S151 PBFT-lite — f=1 ⇒ צריך 2f+1=3 הצבעות prepare/commit */
function bftThreshold(f = 1) { return 2 * Math.max(0, f | 0) + 1; }

function bftPropose(valueDigest, opts = {}) {
  const digest = String(valueDigest || '');
  if (!digest) return { ok: false, reason: 'empty-digest' };
  const view = opts.view | 0;
  const seq = opts.seq | 0;
  const proposer = String(opts.proposer || 'self');
  return {
    ok: true,
    phase: 'pre-prepare',
    view, seq, digest, proposer,
    id: hex(sha256(Buffer.from(`bft:pp:${view}:${seq}:${digest}`))).slice(0, 24),
  };
}

function bftVote(proposal, voterPub, phase = 'prepare') {
  if (!proposal?.ok && !proposal?.digest) return { ok: false, reason: 'bad-proposal' };
  const digest = proposal.digest;
  const view = proposal.view | 0;
  const seq = proposal.seq | 0;
  const voter = String(voterPub || '');
  if (!voter) return { ok: false, reason: 'no-voter' };
  const ph = phase === 'commit' ? 'commit' : 'prepare';
  const ballot = hex(sha256(Buffer.from(`bft:${ph}:${view}:${seq}:${digest}:${voter}`))).slice(0, 32);
  let sig = null;
  try { if (keys?.priv) sig = schnorrSign(ballot, keys.priv); } catch { /* no keys in pure plan */ }
  return { ok: true, phase: ph, view, seq, digest, voter, ballot, sig };
}

function bftCommit(votes, opts = {}) {
  const f = opts.f != null ? opts.f : 1;
  const need = bftThreshold(f);
  const digest = opts.digest || (votes?.[0] && votes[0].digest);
  if (!digest) return { ok: false, reason: 'no-digest', need, agree: 0 };
  const prep = new Set();
  const comm = new Set();
  for (const v of votes || []) {
    if (!v?.ok || v.digest !== digest) continue;
    if (v.phase === 'prepare') prep.add(v.voter);
    if (v.phase === 'commit') comm.add(v.voter);
  }
  const prepareOk = prep.size >= need;
  const commitOk = comm.size >= need;
  return {
    ok: prepareOk && commitOk,
    digest, need, f,
    prepare: prep.size, commit: comm.size,
    phase: commitOk ? 'committed' : prepareOk ? 'prepared' : 'voting',
    reason: commitOk ? 'bft-committed' : 'bft-insufficient-quorum',
  };
}

function bftStatusPlan(state = {}, opts = {}) {
  const f = opts.f != null ? opts.f : (typeof LATTICE_WAVE !== 'undefined' ? LATTICE_WAVE.bftF : 1);
  return {
    ok: true,
    enabled: !!(typeof BFT_SYNC !== 'undefined' ? BFT_SYNC : opts.enabled),
    f, need: bftThreshold(f),
    lastDigest: state.lastDigest || state.digest || null,
    lastPhase: state.lastPhase || state.phase || 'idle',
    voters: state.voters || [],
    prepare: state.prepare ?? 0,
    commit: state.commit ?? 0,
    proposal: state.proposal || null,
    committed: !!state.committed,
    reason: 'bft-status',
  };
}

function bftGateAccept(digest, votes, opts = {}) {
  if (opts.enabled === false || (typeof BFT_SYNC !== 'undefined' && !BFT_SYNC && opts.force !== true)) {
    return { ok: true, skipped: true, reason: 'bft-disabled' };
  }
  const c = bftCommit(votes, { digest, f: opts.f });
  return { ...c, gated: true };
}

/** S152 Offline commit queue */
function offlineCommitPlan(op, prev = [], opts = {}) {
  const max = opts.max || (typeof LATTICE_WAVE !== 'undefined' ? LATTICE_WAVE.offlineQueueMax : 10000);
  const seq = (prev.length ? (prev[prev.length - 1].seq || 0) : 0) + 1;
  const payload = typeof op === 'string' ? op : JSON.stringify(op ?? {});
  const digest = hex(sha256(Buffer.from('offline:' + seq + ':' + payload))).slice(0, 32);
  if (!opts.hmacKey && !opts.allowDemoHmac && !process.env.SC_TEST) {
    return { ok: false, reason: 'offline-hmac-key-required', hint: 'pass hmacKey or keys.enc' };
  }
  const secret = opts.hmacKey || Buffer.from('offline-hmac-demo');
  const mac = hex(crypto.createHmac('sha256', secret).update(digest + payload).digest()).slice(0, 32);
  const entry = {
    seq, digest, payloadEnc: Buffer.from(payload).toString('base64'),
    ts: opts.ts || Date.now(), status: 'queued', mac,
  };
  const queue = prev.concat([entry]).slice(-max);
  return { ok: true, entry, queue, depth: queue.length };
}

function drainOfflineQueuePlan(queue, opts = {}) {
  const alive = opts.poolAlive !== false;
  if (!alive) return { ok: false, drained: [], remaining: queue || [], reason: 'pool-down' };
  const q = Array.isArray(queue) ? queue.slice() : [];
  q.sort((a, b) => (a.seq || 0) - (b.seq || 0));
  const batch = Math.max(1, opts.batch || 8);
  const drained = [];
  const remaining = [];
  for (let i = 0; i < q.length; i++) {
    if (opts.hmacKey && !offlineCommitVerify(q[i], opts)) {
      remaining.push({ ...q[i], status: 'mac-fail' });
      continue;
    }
    if (drained.length < batch) drained.push({ ...q[i], status: 'drained' });
    else remaining.push(q[i]);
  }
  const seqs = drained.map((e) => e.seq);
  const ordered = seqs.every((s, i) => i === 0 || s > seqs[i - 1]);
  return { ok: true, drained, remaining, ordered, reason: 'offline-drain' };
}

function applyOfflineOpPlan(op) {
  if (op == null) return { ok: false, reason: 'empty-op' };
  if (typeof op === 'object' && op.op === 'set' && (op.key != null || op.k != null)) {
    return { ok: true, kind: 'set', key: op.key ?? op.k, value: op.value ?? op.v };
  }
  if (typeof op === 'object' && op.key != null && op.value !== undefined) {
    return { ok: true, kind: 'set', key: op.key, value: op.value };
  }
  if (typeof op === 'object' && op.set != null && typeof op.set === 'object' && !Array.isArray(op.set)) {
    const key = op.set.key ?? op.set.k;
    const value = op.set.value ?? op.set.v ?? op.set;
    if (key != null) return { ok: true, kind: 'set', key, value };
  }
  if (typeof op === 'object' && op.set != null) {
    const key = op.set.key ?? op.set.k ?? String(op.set);
    const value = op.set.value ?? op.set.v ?? op.set;
    return { ok: true, kind: 'set', key, value };
  }
  if (typeof op === 'object' && op.kv) {
    return { ok: true, kind: 'set', key: op.kv.key ?? op.kv.k, value: op.kv.value ?? op.kv.v };
  }
  return { ok: true, kind: 'raw', op };
}

/** S152 — מיזוג drained ops ל-KV מקומי (טהור, לבדיקות + drain) */
function applyOfflineDrainPlan(kv, drainedEntries, opts = {}) {
  const out = { ...(kv || {}) };
  let changed = 0;
  const ts = opts.ts || Date.now();
  for (const e of drainedEntries || []) {
    try {
      const op = e.op != null
        ? e.op
        : JSON.parse(Buffer.from(e.payloadEnc, 'base64').toString('utf8'));
      const plan = applyOfflineOpPlan(op);
      if (plan.ok && plan.kind === 'set' && plan.key != null) {
        out[plan.key] = { v: plan.value, ts: ts + changed };
        changed++;
      }
    } catch { /* skip bad entry */ }
  }
  return { ok: true, kv: out, changed, reason: 'offline-drain-apply' };
}

function offlineQueueStatusPlan(queue) {
  const q = queue || [];
  return { ok: true, depth: q.length, pending: q.filter((e) => e.status === 'queued').length, reason: 'offline-queue-status' };
}

function offlineCommitVerify(entry, opts = {}) {
  if (!entry?.digest || !entry?.payloadEnc || !entry?.mac) return false;
  const secret = opts.hmacKey || Buffer.from('offline-hmac-demo');
  const payload = Buffer.from(entry.payloadEnc, 'base64').toString('utf8');
  const expect = hex(crypto.createHmac('sha256', secret).update(entry.digest + payload).digest()).slice(0, 32);
  const got = String(entry.mac);
  if (got.length !== expect.length) return false;
  try {
    return crypto.timingSafeEqual(Buffer.from(got, 'utf8'), Buffer.from(expect, 'utf8'));
  } catch {
    return false;
  }
}

/** S153 Hybrid Lattice KEM (educational) + AES-GCM */
function _latPoly(seed, tag) {
  const out = new Int16Array(LATTICE_N);
  let h = sha256(Buffer.from(String(seed) + ':' + tag));
  for (let i = 0; i < LATTICE_N; i++) {
    out[i] = ((h[i % 32] | (h[(i + 1) % 32] << 8)) % LATTICE_Q);
    if (i % 8 === 7) h = sha256(h);
  }
  return out;
}

function _latNoise(seed, tag) {
  const out = new Int16Array(LATTICE_N);
  const h = sha256(Buffer.from('noise:' + seed + ':' + tag));
  for (let i = 0; i < LATTICE_N; i++) out[i] = (h[i] % (2 * LATTICE_NOISE + 1)) - LATTICE_NOISE;
  return out;
}

function _latAdd(a, b) {
  const o = new Int16Array(LATTICE_N);
  for (let i = 0; i < LATTICE_N; i++) o[i] = ((a[i] + b[i]) % LATTICE_Q + LATTICE_Q) % LATTICE_Q;
  return o;
}

function _latDot(a, b) {
  let s = 0;
  for (let i = 0; i < LATTICE_N; i++) s = (s + a[i] * b[i]) % LATTICE_Q;
  return (s + LATTICE_Q) % LATTICE_Q;
}

function _latSer(p) { return Buffer.from(p.buffer, p.byteOffset, p.byteLength).toString('base64'); }
function _latDes(b64) {
  const buf = Buffer.from(b64, 'base64');
  return new Int16Array(buf.buffer, buf.byteOffset, LATTICE_N);
}

function latticeKemKeygen(seed = crypto.randomBytes(16).toString('hex')) {
  const s = _latPoly(seed, 'sk');
  const a = _latPoly(seed, 'a');
  const e = _latNoise(seed, 'e');
  const b = _latAdd(_latPoly(String(_latDot(a, s)), 'bscale'), e);
  // educational: pk = (a, b≈a·s+e), sk = s
  return {
    ok: true, seed,
    pk: { a: _latSer(a), b: _latSer(b) },
    sk: { s: _latSer(s) },
    honesty: 'educational-lattice-kem-not-mlkem',
  };
}

function latticeKemEncaps(pk, seed = crypto.randomBytes(16).toString('hex')) {
  if (!pk?.a || !pk?.b) return { ok: false, reason: 'bad-pk' };
  const a = _latDes(pk.a);
  const b = _latDes(pk.b);
  const r = _latPoly(seed, 'r');
  const e1 = _latNoise(seed, 'e1');
  const uu = _latAdd(a, e1);
  const shared = sha256(Buffer.from('lat-shared:' + seed));
  const mBit = shared[0] & 1;
  const v = (_latDot(b, r) + mBit * Math.floor(LATTICE_Q / 2) + (_latNoise(seed, 'e2')[0])) % LATTICE_Q;
  return {
    ok: true,
    ct: { u: _latSer(uu), v: v, seedHint: hex(sha256(Buffer.from(seed))).slice(0, 16) },
    shared: hex(shared),
  };
}

function latticeKemDecaps(sk, ct) {
  if (!sk?.s || !ct?.u) return { ok: false, reason: 'bad-ct' };
  // Educational recover: re-derive from seedHint chain is not possible without seed;
  // Instead store shared in ct for toy roundtrip via sealed hybrid — use v decode:
  const s = _latDes(sk.s);
  const u = _latDes(ct.u);
  const approx = _latDot(u, s);
  const diff = ((ct.v - approx) % LATTICE_Q + LATTICE_Q) % LATTICE_Q;
  const mBit = diff > LATTICE_Q / 4 && diff < 3 * LATTICE_Q / 4 ? 1 : 0;
  // Toy shared = H(sk || ct) — deterministic for same key+ct (educational continuity)
  const shared = sha256(Buffer.from('lat-dec:' + sk.s + ':' + ct.u + ':' + ct.v + ':' + mBit));
  return { ok: true, shared: hex(shared), mBit };
}

function hybridWrap(plaintext, opts = {}) {
  const ecdh = opts.ecdhKey32 || sha256(Buffer.from(opts.ecdhSeed || 'ecdh-demo'));
  const kem = latticeKemKeygen(opts.kemSeed || crypto.randomBytes(8).toString('hex'));
  const enc = latticeKemEncaps(kem.pk, opts.encSeed || crypto.randomBytes(8).toString('hex'));
  // For educational roundtrip: combine ECDH with encaps shared; store encaps shared in package
  const key = sha256(Buffer.concat([
    Buffer.isBuffer(ecdh) ? ecdh : Buffer.from(ecdh),
    Buffer.from(enc.shared, 'hex'),
  ]));
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', key, iv);
  c.setAAD(Buffer.from('hybrid-lat:v1'));
  const pt = Buffer.isBuffer(plaintext) ? plaintext : Buffer.from(String(plaintext));
  const ct = Buffer.concat([c.update(pt), c.final()]);
  const tag = c.getAuthTag();
  return {
    ok: true,
    honesty: 'hybrid-aes-gcm + educational-lattice — not NIST PQC',
    pkg: {
      iv: iv.toString('base64'),
      ct: ct.toString('base64'),
      tag: tag.toString('base64'),
      kemPk: kem.pk,
      kemCt: { ...enc.ct, shared: enc.shared },
      ecdhHint: hex(sha256(Buffer.isBuffer(ecdh) ? ecdh : Buffer.from(ecdh))).slice(0, 16),
    },
  };
}

function hybridOpen(pkg, opts = {}) {
  if (!pkg?.ct || !pkg?.kemCt) return { ok: false, reason: 'bad-pkg' };
  const ecdh = opts.ecdhKey32 || sha256(Buffer.from(opts.ecdhSeed || 'ecdh-demo'));
  const shared = pkg.kemCt.shared || latticeKemDecaps(pkg.kemSk, pkg.kemCt).shared;
  const key = sha256(Buffer.concat([
    Buffer.isBuffer(ecdh) ? ecdh : Buffer.from(ecdh),
    Buffer.from(shared, 'hex'),
  ]));
  try {
    const d = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(pkg.iv, 'base64'));
    d.setAAD(Buffer.from('hybrid-lat:v1'));
    d.setAuthTag(Buffer.from(pkg.tag, 'base64'));
    const pt = Buffer.concat([d.update(Buffer.from(pkg.ct, 'base64')), d.final()]);
    return { ok: true, plaintext: pt };
  } catch (e) {
    return { ok: false, reason: 'tamper-or-bad-key', error: e.message };
  }
}

/** S154 Σ-protocol ZK auth (Schnorr-like on discrete-log toy group) */
function zkAuthProve(secret, opts = {}) {
  // Σ-protocol לימודי (hash-based Schnorr-like) — לא חושף את הסוד ב־transcript
  const X = hex(sha256(Buffer.from('zk-X:' + String(secret))));
  const R = hex(sha256(Buffer.from('zk-R:' + (opts.nonce || crypto.randomBytes(16).toString('hex')))));
  const challenge = hex(sha256(Buffer.from('zk-c:' + X + ':' + R + ':' + (opts.context || '')))).slice(0, 32);
  const response = hex(sha256(Buffer.from('zk-s:' + String(secret) + ':' + R + ':' + challenge))).slice(0, 40);
  return {
    ok: true,
    transcript: { X, R, challenge, response, context: opts.context || '' },
  };
}

function zkAuthVerify(transcript, opts = {}) {
  if (!transcript?.X || !transcript?.R || !transcript?.challenge || !transcript?.response) {
    return { ok: false, reason: 'bad-transcript' };
  }
  const raw = JSON.stringify(transcript);
  if (opts.forbidSecret && raw.includes(String(opts.forbidSecret))) {
    return { ok: false, reason: 'secret-leaked-in-transcript' };
  }
  // Verifier recomputes challenge binding
  const c2 = hex(sha256(Buffer.from('zk-c:' + transcript.X + ':' + transcript.R + ':' + (transcript.context || '')))).slice(0, 32);
  if (c2 !== transcript.challenge) return { ok: false, reason: 'bad-challenge' };
  // Knowledge check: prover must supply response that matches X for claimed secret OR opts.checkSecret
  if (opts.checkSecret != null) {
    const response = hex(sha256(Buffer.from('zk-s:' + String(opts.checkSecret) + ':' + transcript.R + ':' + transcript.challenge))).slice(0, 40);
    if (response !== transcript.response) return { ok: false, reason: 'bad-response' };
    const X = hex(sha256(Buffer.from('zk-X:' + String(opts.checkSecret))));
    if (X !== transcript.X) return { ok: false, reason: 'bad-X' };
  } else if (opts.expectedX) {
    if (opts.expectedX !== transcript.X) return { ok: false, reason: 'identity-mismatch' };
    // structural binding only — knowledge requires checkSecret (educational Σ)
  } else {
    return { ok: false, reason: 'need-checkSecret-or-expectedX' };
  }
  return { ok: true, reason: 'zk-verified' };
}

/** S155 Edge mapreduce helpers (ops also in XI_OPS) */
function edgeMapReducePlan(job, shards, opts = {}) {
  const results = [];
  const list = Array.isArray(shards) ? shards : [shards];
  for (let i = 0; i < list.length; i++) {
    const program = typeof job === 'function' ? job(list[i], i) : { ...(job || {}), args: { ...(job?.args || {}), shard: list[i], i } };
    const solved = xiSolve(program.op ? program : { op: 'reduce', args: { op: 'sum', list: [].concat(list[i]) } });
    results.push({ author: opts.authors?.[i] || ('edge-' + i), digest: solved.digest, result: solved.result });
  }
  const q = xiQuorum(results, opts.quorum || Math.min(2, results.length));
  return { ok: q.ok, quorum: q, results, reason: q.ok ? 'edge-mapreduce-ok' : 'edge-quorum-fail' };
}

/** S156 Stego PNG LSB + covert HTTP */
function _crc32(buf) {
  let c = ~0;
  for (let i = 0; i < buf.length; i++) {
    c ^= buf[i];
    for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
  }
  return ~c >>> 0;
}

function _pngChunk(type, data) {
  const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
  const typ = Buffer.from(type, 'ascii');
  const crc = Buffer.alloc(4);
  crc.writeUInt32BE(_crc32(Buffer.concat([typ, data])));
  return Buffer.concat([len, typ, data, crc]);
}

function stegoEmbedPng(payload, opts = {}) {
  const data = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload));
  const mac = crypto.createHmac('sha256', Buffer.from(opts.macKey || 'stego-mac')).update(data).digest().subarray(0, 8);
  const blob = Buffer.concat([Buffer.from([data.length >> 8, data.length & 255]), data, mac]);
  const w = opts.w || 16, h = opts.h || 16;
  const raw = Buffer.alloc((w * h * 4) + h); // filter byte per row
  let bi = 0;
  for (let y = 0; y < h; y++) {
    raw[y * (w * 4 + 1)] = 0;
    for (let x = 0; x < w; x++) {
      const o = y * (w * 4 + 1) + 1 + x * 4;
      raw[o] = 40; raw[o + 1] = 80; raw[o + 2] = 120; raw[o + 3] = 255;
      for (let c = 0; c < 3 && bi < blob.length * 8; c++) {
        const bit = (blob[bi >> 3] >> (7 - (bi & 7))) & 1;
        raw[o + c] = (raw[o + c] & 0xfe) | bit;
        bi++;
      }
    }
  }
  const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
  const ihdr = Buffer.alloc(13);
  ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4);
  ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
  const idat = zlib.deflateSync(raw);
  const png = Buffer.concat([sig, _pngChunk('IHDR', ihdr), _pngChunk('IDAT', idat), _pngChunk('IEND', Buffer.alloc(0))]);
  return { ok: true, png, bytes: blob.length };
}

function stegoExtractPng(pngBuf, opts = {}) {
  try {
    const buf = Buffer.isBuffer(pngBuf) ? pngBuf : Buffer.from(pngBuf);
    if (buf[0] !== 137 || buf[1] !== 80) return { ok: false, reason: 'not-png' };
    let off = 8, idat = [];
    let w = 0, h = 0;
    while (off + 8 <= buf.length) {
      const len = buf.readUInt32BE(off); const type = buf.toString('ascii', off + 4, off + 8);
      const data = buf.subarray(off + 8, off + 8 + len);
      if (type === 'IHDR') { w = data.readUInt32BE(0); h = data.readUInt32BE(4); }
      if (type === 'IDAT') idat.push(data);
      if (type === 'IEND') break;
      off += 12 + len;
    }
    const raw = zlib.inflateSync(Buffer.concat(idat));
    const bits = [];
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        const o = y * (w * 4 + 1) + 1 + x * 4;
        for (let c = 0; c < 3; c++) bits.push(raw[o + c] & 1);
      }
    }
    const bytes = [];
    for (let i = 0; i + 8 <= bits.length; i += 8) {
      let v = 0;
      for (let b = 0; b < 8; b++) v = (v << 1) | bits[i + b];
      bytes.push(v);
    }
    if (bytes.length < 2) return { ok: false, reason: 'short' };
    const len = (bytes[0] << 8) | bytes[1];
    const data = Buffer.from(bytes.slice(2, 2 + len));
    const mac = Buffer.from(bytes.slice(2 + len, 2 + len + 8));
    if (mac.length !== 8) return { ok: false, reason: 'mac-short' };
    const expect = crypto.createHmac('sha256', Buffer.from(opts.macKey || 'stego-mac')).update(data).digest().subarray(0, 8);
    if (!crypto.timingSafeEqual(mac, expect)) return { ok: false, reason: 'mac-fail' };
    return { ok: true, payload: data };
  } catch (e) {
    return { ok: false, reason: 'extract-fail', error: e.message };
  }
}

function covertHttpWrap(payload, opts = {}) {
  const raw = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload));
  const key = sha256(Buffer.from(opts.key || 'covert-http'));
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', key, iv);
  const ct = Buffer.concat([c.update(raw), c.final()]);
  const tag = c.getAuthTag();
  return {
    ok: true,
    body: {
      headline: opts.headline || 'Local weather update',
      summary: 'Sunny intervals expected.',
      ts: Date.now(),
      media: Buffer.concat([iv, tag, ct]).toString('base64'),
    },
  };
}

function covertHttpUnwrap(body, opts = {}) {
  try {
    const key = sha256(Buffer.from(opts.key || 'covert-http'));
    const blob = Buffer.from(body.media || body, 'base64');
    const iv = blob.subarray(0, 12);
    const tag = blob.subarray(12, 28);
    const ct = blob.subarray(28);
    const d = crypto.createDecipheriv('aes-256-gcm', key, iv);
    d.setAuthTag(tag);
    return { ok: true, payload: Buffer.concat([d.update(ct), d.final()]) };
  } catch (e) {
    return { ok: false, reason: 'unwrap-fail', error: e.message };
  }
}

/** S156 COVERT_MESH — ingest covert carrier from webkv */
function covertMeshIngestPlan(body, opts = {}) {
  if (!body) return { ok: false, reason: 'empty-body' };
  const un = covertHttpUnwrap(body, { key: opts.key || 'covert-http' });
  if (!un.ok) return un;
  return {
    ok: true,
    payload: un.payload,
    digest: hex(sha256(un.payload)).slice(0, 32),
    reason: 'covert-mesh-ingest',
  };
}

/** S157 WebRTC / P2P signaling state machine */
function p2pSignalPlan(action, state = {}, msg = {}) {
  const st = { phase: state.phase || 'idle', local: state.local || null, remote: state.remote || null, ice: state.ice || [], dc: state.dc || false };
  if (action === 'offer') {
    st.phase = 'have-local-offer';
    st.local = { type: 'offer', sdp: msg.sdp || ('v=0\r\no=- ' + Date.now()), digest: hex(sha256(Buffer.from(msg.sdp || 'offer'))).slice(0, 16) };
  } else if (action === 'answer') {
    if (st.phase !== 'have-local-offer' && st.phase !== 'have-remote-offer') {
      /* allow answer after remote offer */
    }
    st.phase = 'stable';
    st.remote = { type: 'answer', sdp: msg.sdp || 'answer', digest: hex(sha256(Buffer.from(msg.sdp || 'answer'))).slice(0, 16) };
  } else if (action === 'remote-offer') {
    st.phase = 'have-remote-offer';
    st.remote = { type: 'offer', sdp: msg.sdp || 'offer' };
  } else if (action === 'ice') {
    st.ice.push(msg.candidate || 'candidate:1');
  } else if (action === 'dc-open') {
    st.dc = true; st.phase = 'connected';
  } else if (action === 'dc-fail') {
    st.dc = false; st.phase = 'fallback-relay';
  }
  return { ok: true, state: st, reason: 'p2p-' + action };
}

function p2pSyncOncePlan(peer, fileDigest, state, opts = {}) {
  let s = p2pSignalPlan('offer', state, { sdp: 'offer-' + peer });
  s = p2pSignalPlan('answer', s.state, { sdp: 'answer-' + peer });
  s = p2pSignalPlan('dc-open', s.state, {});
  if (!s.state.dc && opts.allowFallback !== false) {
    return { ok: true, path: 'relay-fallback', digest: fileDigest, peer, state: p2pSignalPlan('dc-fail', s.state).state };
  }
  return { ok: true, path: 'datachannel', digest: fileDigest, peer, state: s.state };
}

/** S158 Opt-in Node Agent */
function nodeAgentTickPlan(telemetry = {}, opts = {}) {
  const enabled = opts.enabled != null ? opts.enabled : (typeof NODE_AGENT !== 'undefined' && NODE_AGENT);
  if (!enabled) return { ok: true, enabled: false, actions: [], reason: 'agent-off' };
  const actions = [];
  if ((telemetry.buildErrors || 0) > 0) actions.push({ op: 'heal-hint', detail: 'retry-build' });
  if ((telemetry.chunkBytes || 0) > 1_000_000) actions.push({ op: 'chunk-hint', detail: 'split-chunks' });
  if (telemetry.needCompute) actions.push({ op: 'edgeMapReduce', detail: 'spawn-job' });
  const apiEndpoint = (typeof NODE_AGENT_API !== 'undefined' && NODE_AGENT_API) ? NODE_AGENT_API : null;
  if (apiEndpoint) actions.push({ op: 'api-ping', detail: apiEndpoint });
  return { ok: true, enabled: true, actions, reason: 'agent-tick', apiEndpoint };
}

function nodeAgentMessagePlan(from, to, body, opts = {}) {
  if (!opts.fromEnabled || !opts.toEnabled) return { ok: false, reason: 'peer-agent-off' };
  if (opts.requireSameId && opts.idA !== opts.idB) return { ok: false, reason: 'sync-id-mismatch' };
  const payload = JSON.stringify({ from, to, body, ts: Date.now() });
  const key = sha256(Buffer.from(opts.channelKey || 'agent-chan'));
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', key, iv);
  const ct = Buffer.concat([c.update(payload), c.final()]);
  return { ok: true, envelope: 'agent://' + Buffer.concat([iv, c.getAuthTag(), ct]).toString('base64') };
}

/** S159 Toy FHE — legacy Paillier-lite removed; use fheEncSimple/fheAddSimple */
function fheKeygen(opts = {}) {
  return { ok: true, pk: { scheme: 'additive-toy' }, sk: { scheme: 'additive-toy' }, honesty: 'toy-fhe-not-production' };
}

/** S159 Toy FHE — additive helpers (fheEncSimple/fheAddSimple defined before XI_OPS) */
function fheMulSimple(a, scalar) {
  const s = BigInt(scalar);
  return { ok: true, ct: { c: (BigInt(a.ct.c) * s).toString(), r: (BigInt(a.ct.r) * s).toString() } };
}
function fheDecSimple(key, ct) {
  const k = BigInt('0x' + hex(sha256(Buffer.from('fhe-k:' + key))).slice(0, 12));
  const m = BigInt(ct.c) - k * BigInt(ct.r);
  return { ok: true, m: Number(m) };
}

/** S160 Swarm path weights */
function swarmScore(peer, metric = {}, weights = {}) {
  const w = weights[peer] != null ? weights[peer] : 1;
  const lat = metric.latency || 100;
  const fails = metric.fails || 0;
  const score = w * (1000 / (lat + 1)) * Math.pow(0.5, fails);
  return { ok: true, peer, score };
}

function swarmUpdateWeights(weights, peer, success, opts = {}) {
  const decay = opts.decay != null ? opts.decay : (typeof LATTICE_WAVE !== 'undefined' ? LATTICE_WAVE.swarmDecay : 0.92);
  const next = { ...(weights || {}) };
  const cur = next[peer] != null ? next[peer] : 1;
  next[peer] = success ? Math.min(8, cur * 1.15) : Math.max(0.05, cur * decay);
  return { ok: true, weights: next, peer, value: next[peer] };
}

function swarmPickPath(candidates, weights, metrics = {}) {
  let best = null;
  for (const p of candidates || []) {
    const s = swarmScore(p, metrics[p] || {}, weights).score;
    if (!best || s > best.score) best = { peer: p, score: s };
  }
  return { ok: !!best, pick: best?.peer || null, score: best?.score || 0 };
}

/** S161 Opaque Oracle */
function oracleFetchPlan(url, exitResults, opts = {}) {
  const need = opts.min || (typeof LATTICE_WAVE !== 'undefined' ? LATTICE_WAVE.oracleMin : 3);
  const by = new Map();
  for (const r of exitResults || []) {
    if (!r?.bodyHash) continue;
    if (!by.has(r.bodyHash)) by.set(r.bodyHash, { hash: r.bodyHash, authors: new Set(), enc: r.enc });
    if (!r.author) continue;
    by.get(r.bodyHash).authors.add(r.author);
  }
  let best = null;
  for (const g of by.values()) {
    if (!best || g.authors.size > best.agree) best = { hash: g.hash, enc: g.enc, agree: g.authors.size };
  }
  const ok = !!(best && best.agree >= need);
  return { ok, url, need, agree: best?.agree || 0, bodyHash: best?.hash || null, reason: ok ? 'oracle-consensus' : 'oracle-no-quorum' };
}

/** S162 Vector + CRDT */
function crdtGCounterInc(counter, actor, n = 1) {
  const c = { ...(counter || {}) };
  c[actor] = (c[actor] || 0) + n;
  return c;
}
function crdtGCounterValue(counter) {
  return Object.values(counter || {}).reduce((a, b) => a + b, 0);
}
function crdtGCounterMerge(a, b) {
  const out = { ...(a || {}) };
  for (const [k, v] of Object.entries(b || {})) out[k] = Math.max(out[k] || 0, v);
  return out;
}
function crdtLWWSet(reg, value, ts, actor) {
  if (!reg || ts > reg.ts || (ts === reg.ts && String(actor) > String(reg.actor))) {
    return { value, ts, actor };
  }
  return reg;
}
function crdtLWWMerge(a, b) {
  if (!a) return b;
  if (!b) return a;
  return crdtLWWSet(a, b.value, b.ts, b.actor);
}
function crdtORMapApply(map, key, value, tag) {
  const m = { ...(map || {}) };
  m[key] = { value, tags: [...new Set([...(m[key]?.tags || []), tag])] };
  return m;
}
function crdtORMapMerge(a, b) {
  const out = { ...(a || {}) };
  for (const [k, v] of Object.entries(b || {})) {
    const tags = [...new Set([...(out[k]?.tags || []), ...(v.tags || [])])];
    const prefer = !out[k] || String(v.value) > String(out[k].value) ? v.value : out[k].value;
    out[k] = { value: prefer, tags };
  }
  return out;
}
function crdtApplyPlan(state, op) {
  const s = state || { g: {}, lww: null, or: {} };
  if (op.type === 'g-inc') s.g = crdtGCounterInc(s.g, op.actor, op.n || 1);
  else if (op.type === 'lww') s.lww = crdtLWWSet(s.lww, op.value, op.ts || Date.now(), op.actor);
  else if (op.type === 'or-set') s.or = crdtORMapApply(s.or, op.key, op.value, op.tag || op.actor);
  else if (op.type === 'merge') {
    s.g = crdtGCounterMerge(s.g, op.g || {});
    s.lww = crdtLWWMerge(s.lww, op.lww);
    s.or = crdtORMapMerge(s.or, op.or || {});
  }
  return { ok: true, state: s };
}
function vsearchPlan(docs, query, opts = {}) {
  const q = embedLattice(query, opts);
  const scored = (docs || []).map((d) => ({
    id: d.id, score: cosineSim(q, d.vec || embedLattice(d.text || '', opts)),
  })).sort((a, b) => b.score - a.score);
  return { ok: true, hits: scored.slice(0, opts.k || 5) };
}

/** S163 DTN multi-transport */
function transportEnqueuePlan(klass, bundle, queue = []) {
  const allowed = new Set(['dtn', 'lan-file', 'bluetooth-sim']);
  if (!allowed.has(klass)) return { ok: false, reason: 'bad-transport' };
  const entry = {
    class: klass,
    bundle: bundle || {},
    id: hex(sha256(Buffer.from(JSON.stringify(bundle || {}) + klass))).slice(0, 20),
    ts: Date.now(), status: 'custody',
  };
  return { ok: true, entry, queue: queue.concat([entry]), honesty: 'logical-transport-not-physical-lora' };
}
function transportForwardPlan(queue, peerMet = false) {
  if (!peerMet) return { ok: true, forwarded: [], remaining: queue || [], reason: 'dark-hold' };
  return { ok: true, forwarded: queue || [], remaining: [], reason: 'store-and-forward' };
}

/** S164 AXTree */
function domToAxTree(html) {
  const text = String(html || '');
  const refs = [];
  const re = /<(button|a|input|textarea)([^>]*)>([^<]*)/gi;
  let m, i = 1;
  while ((m = re.exec(text))) {
    const tag = m[1].toLowerCase();
    const attrs = m[2] || '';
    const name = (m[3] || '').trim() || (attrs.match(/placeholder="([^"]*)"/) || [])[1] || tag;
    const type = (attrs.match(/type="([^"]*)"/) || [])[1] || tag;
    refs.push({ ref: 'e' + (i++), role: tag === 'a' ? 'link' : tag === 'button' ? 'button' : 'textbox', name, type });
  }
  return { ok: true, refs, n: refs.length, htmlBytes: text.length, axBytes: JSON.stringify(refs).length };
}
function axAction(tree, action) {
  const state = { ...(tree.state || {}), values: { ...(tree.state?.values || {}) } };
  const ref = action.ref;
  const node = (tree.refs || []).find((r) => r.ref === ref);
  if (!node) return { ok: false, reason: 'unknown-ref', state };
  if (action.action === 'click') state.lastClick = ref;
  if (action.action === 'type') state.values[ref] = action.text || '';
  return { ok: true, state: { ...tree, state }, reason: 'ax-action' };
}

/** S165 Session vault */
function sessionVaultSeal(cookies, syncId, opts = {}) {
  const salt = crypto.randomBytes(16);
  const key = sha256(Buffer.concat([Buffer.from('session-vault:' + String(syncId || '')), salt]));
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', key, iv);
  const pt = Buffer.from(JSON.stringify(cookies || {}));
  const ct = Buffer.concat([c.update(pt), c.final()]);
  const tag = c.getAuthTag();
  return { ok: true, v: 2, blob: Buffer.concat([salt, iv, tag, ct]).toString('base64') };
}
function sessionVaultOpen(blob, syncId) {
  try {
    const raw = Buffer.from(blob, 'base64');
    let off = 0;
    let key;
    if (raw.length > 40) {
      key = sha256(Buffer.concat([Buffer.from('session-vault:' + String(syncId || '')), raw.subarray(0, 16)]));
      off = 16;
    } else {
      key = sha256(Buffer.from('session-vault:' + String(syncId || '')));
    }
    const iv = raw.subarray(off, off + 12);
    const tag = raw.subarray(off + 12, off + 28);
    const ct = raw.subarray(off + 28);
    const d = crypto.createDecipheriv('aes-256-gcm', key, iv);
    d.setAuthTag(tag);
    const pt = Buffer.concat([d.update(ct), d.final()]);
    return { ok: true, cookies: JSON.parse(pt.toString()) };
  } catch (e) {
    return { ok: false, reason: e.message };
  }
}

/** S166 RAM sandbox + shred */
function sandboxIngest(buf) {
  const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf || '');
  const magic = b.subarray(0, 4).toString('hex');
  const kind = magic.startsWith('4d5a') ? 'PE' : magic.startsWith('7f454c46') ? 'ELF' : magic.startsWith('89504e47') ? 'PNG' : 'unknown';
  const hash = hex(sha256(b));
  const suspicious = kind === 'PE' || kind === 'ELF';
  return { ok: true, kind, hash, bytes: b.length, suspicious, diskWrite: false };
}
function cryptoShred(buf) {
  const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf || []);
  for (let p = 0; p < 3; p++) crypto.randomFillSync(b);
  b.fill(0);
  return { ok: true, zeroed: b.every((x) => x === 0), bytes: b.length };
}

/** S167 Metamorph dispatch */
function metamorphPlan(seed) {
  const keys = ['encrypt', 'hash', 'route', 'pad', 'hop'];
  const order = shuffle(keys.map((k, i) => ({ k, i: (sha256(Buffer.from(seed + k))[0] + i) % 97 }))).sort((a, b) => a.i - b.i).map((x) => x.k);
  // deterministic shuffle from seed:
  const rnd = mulberry32(seedFromStr(String(seed)));
  const o = keys.slice();
  for (let i = o.length - 1; i > 0; i--) {
    const j = Math.floor(rnd() * (i + 1));
    [o[i], o[j]] = [o[j], o[i]];
  }
  return { ok: true, order: o, fingerprint: hex(sha256(Buffer.from(o.join('|')))).slice(0, 16) };
}
function metamorphDispatch(plan, input, handlers) {
  let v = input;
  for (const step of plan.order || []) {
    const fn = handlers[step] || ((x) => x);
    v = fn(v);
  }
  return { ok: true, output: v, fingerprint: plan.fingerprint };
}

/** §17 Sibling auto-push */
function siblingNeedsPushPlan(localSeq, peers) {
  const lagging = (peers || []).filter((p) => (p.engineSeq || 0) < (localSeq || 0));
  return { ok: true, needsPush: lagging.length > 0, lagging, localSeq };
}

/** S168–S172 continuation doors */
function bftOracleBridgePlan(oracle, bftVotes, opts = {}) {
  if (!oracle?.ok) return { ok: false, reason: 'oracle-failed' };
  const gate = bftGateAccept(oracle.bodyHash, bftVotes, { enabled: true, f: opts.f || 1, force: true });
  return { ok: gate.ok, door: 'S168', oracle, bft: gate };
}
function fheEdgeJobPlan(values, key) {
  const enc = values.map((v) => fheEncSimple(key, v));
  let acc = enc[0];
  for (let i = 1; i < enc.length; i++) acc = fheAddSimple(acc, enc[i]);
  const dec = fheDecSimple(key, acc.ct);
  const plain = values.reduce((a, b) => a + b, 0);
  return { ok: dec.m === plain, door: 'S169', sum: dec.m, plain };
}
function stegoDtnCarrierPlan(payload, queue) {
  const emb = stegoEmbedPng(payload);
  const enq = transportEnqueuePlan('dtn', { png: emb.png.toString('base64'), bytes: emb.bytes }, queue);
  return { ok: emb.ok && enq.ok, door: 'S170', entry: enq.entry, queue: enq.queue };
}
function agentAxLoopPlan(html, agentEnabled) {
  const tree = domToAxTree(html);
  const tick = nodeAgentTickPlan({ needCompute: true }, { enabled: agentEnabled });
  const act = axAction(tree, { action: 'click', ref: tree.refs[0]?.ref });
  return { ok: !!(tree.ok && act.ok), door: 'S171', tree, tick, act };
}
function crdtP2pAntiEntropyPlan(stateA, stateB, peer) {
  const merged = crdtApplyPlan(stateA, { type: 'merge', g: stateB.g, lww: stateB.lww, or: stateB.or });
  const p2p = p2pSyncOncePlan(peer, hex(sha256(Buffer.from(JSON.stringify(merged.state)))).slice(0, 16), {});
  return { ok: merged.ok && p2p.ok, door: 'S172', state: merged.state, p2p };
}

function latticeWaveDoorsCatalog() {
  return ['S151', 'S152', 'S153', 'S154', 'S155', 'S156', 'S157', 'S158', 'S159', 'S160', 'S161', 'S162', 'S163', 'S164', 'S165', 'S166', 'S167', 'S168', 'S169', 'S170', 'S171', 'S172'];
}

const LATTICE_WIRED_DOORS = new Set([
  'S151', 'S152', 'S153', 'S154', 'S155', 'S156', 'S157', 'S158', 'S159', 'S160', 'S161', 'S162',
  'S163', 'S164', 'S165', 'S166', 'S167', 'S168', 'S169', 'S170', 'S171', 'S172',
]);


/** Compose: Roam × S99 health badge — badge משקף roosts חיים */
function roamHealthBadgePlan(roam, sla, opts = {}) {
  const badge = liveAddressHealthBadge(sla || {}, opts);
  const roostN = roam?.n || (roam?.roosts || []).length || 0;
  const alive = Math.max(badge.workersAlive || 0, roostN);
  const level = roostN >= 3 ? 'green' : roostN >= 1 ? (badge.badge || 'yellow') : (badge.badge || 'red');
  return {
    ...badge,
    workersAlive: alive,
    badge: level,
    roostN,
    roamDigest: roam?.roamDigest || null,
    reason: roostN ? 'roam-health-badge' : 'health-without-roam',
  };
}

/** Compose: Hop-Vault × Immortal extinction hazard */
function hopVaultExtinctionHazardPlan(hopVault, history, opts = {}) {
  const placementN = (hopVault?.placement || []).length || hopVault?.n || 0;
  const hist = (history || []).concat([{
    nReplicas: Math.max(placementN, opts.nReplicas || 0),
    nAntennas: Math.max(placementN, opts.nAntennas || 0),
    ts: opts.now || Date.now(),
  }]);
  const hazard = extinctionHazardScore(hist, {
    minReplicas: opts.minReplicas ?? Math.max(3, SPACE_MESH.hopVaultMin || 3),
    minAntennas: opts.minAntennas ?? Math.max(3, SPACE_MESH.hopVaultMin || 3),
    ...opts,
  });
  const thin = !hopVault?.ok || placementN < (opts.minHops ?? SPACE_MESH.hopVaultMin ?? 3);
  return {
    ...hazard,
    placementN,
    thin,
    preempt: hazard.preempt || thin,
    reason: thin ? 'hop-vault-thin' : (hazard.preempt ? 'hop-vault-hazard-preempt' : 'hop-vault-stable'),
    hopVaultDigest: hopVault?.digest || null,
  };
}

/** Compose: Ξ × Sub-Mind × Economy — משימה טהורה עם תקציב על hop */
function xiSubMindEconomyPlan(task, hopPlan, workers, hostCredit, probes, opts = {}) {
  const flock = flockHopSubMindPlan(task, hopPlan, workers, opts);
  const dag = opts.dag || {
    nodes: {
      root: { value: flock.root || 'task' },
      out: { value: flock.nLight + flock.nHeavy },
    },
    output: 'out',
  };
  const xi = xiSatEconomyGate(dag, probes || [], hostCredit, opts);
  if (!xi.ok) return { ok: false, reason: xi.reason, flock, xi, run: false };
  const edges = (hopPlan?.chain || []).length > 1
    ? hopChainCreditGate(hopPlan, hostCredit, { bytesPerHop: opts.bytesPerHop || 32_000 })
    : { ok: true, reason: 'single-or-no-hop', nEdges: 0, balance: hostCredit?.credit ?? hostCredit };
  if (!edges.ok) return { ok: false, reason: edges.reason, flock, xi, edges, run: false };
  const satId = hopPlan?.chain?.[0]?.id || opts.satId || null;
  const receipt = satId ? xiReceiptOnSatPlan(xi, satId, { result: { light: flock.nLight, heavy: flock.nHeavy } }) : null;
  return {
    ok: true, run: true, reason: 'xi-submind-economy',
    flock, xi, edges, receipt, llm: false, pureCompute: true,
  };
}

/** SIOS Economy */
function resourceMeterSample(sample = {}, opts = {}) {
  const cpu = Math.max(0, Math.min(1, sample.cpu ?? sample.cpuLoad ?? 0.1));
  const ram = Math.max(0, Math.min(1, sample.ram ?? sample.mem ?? 0.2));
  const storage = Math.max(0, sample.storageBytes ?? 0);
  const bandwidth = Math.max(0, sample.bandwidthBytes ?? sample.bw ?? 0);
  const hostSec = Math.max(0, sample.hostSec ?? opts.hostSec ?? 1);
  const score = +(0.4 * cpu + 0.3 * ram + 0.2 * Math.min(1, bandwidth / 1e6) + 0.1 * Math.min(1, storage / 1e9)).toFixed(4);
  return { cpu, ram, storage, bandwidth, hostSec, score, ts: opts.now || Date.now() };
}

function resourceCreditPlan(meter, opts = {}) {
  const rate = opts.creditPerHostSec ?? SIOS.creditPerHostSec ?? 0.001;
  const host = meter?.hostSec ?? 0;
  const credit = +(host * rate * (0.5 + (meter?.score || 0.5))).toFixed(6);
  return { credit, rate, hostSec: host, role: 'host', digest: hex(sha256(Buffer.from('econ:cred:' + credit + ':' + host))).slice(0, 24) };
}

function economySettleGate(hostCredit, consumerDebit, opts = {}) {
  const credit = hostCredit?.credit ?? hostCredit ?? 0;
  const debit = consumerDebit?.debit ?? consumerDebit ?? 0;
  const bal = +(credit - debit).toFixed(6);
  if (bal < 0 && !opts.allowDebt) return { ok: false, reason: 'insufficient-credit', balance: bal, credit, debit };
  return { ok: true, reason: 'economy-settled', balance: bal, credit, debit };
}

/** SIOS WASM Runtime — validate + plan (instantiate in Cloud method) */
function wasmModulePlan(bytesOrB64, opts = {}) {
  let buf;
  try {
    if (Buffer.isBuffer(bytesOrB64)) buf = bytesOrB64;
    else if (typeof bytesOrB64 === 'string') buf = Buffer.from(bytesOrB64, opts.encoding || 'base64');
    else if (bytesOrB64 instanceof Uint8Array) buf = Buffer.from(bytesOrB64);
    else return { ok: false, reason: 'bad-bytes' };
  } catch { return { ok: false, reason: 'decode-fail' }; }
  const magic = buf.length >= 4 && buf[0] === 0x00 && buf[1] === 0x61 && buf[2] === 0x73 && buf[3] === 0x6d;
  if (!magic) return { ok: false, reason: 'not-wasm', size: buf.length };
  const digest = hex(sha256(buf)).slice(0, 40);
  return { ok: true, reason: 'wasm-valid', size: buf.length, digest, exportHint: opts.export || 'main' };
}

function secureEvalPlan(kind, payload, opts = {}) {
  if (kind === 'wasm') return { ...wasmModulePlan(payload, opts), kind: 'wasm', sandbox: true };
  if (kind === 'nabla' || kind === 'xi' || kind === 'formula') {
    const dig = hex(sha256(Buffer.from(xiCanon(payload)))).slice(0, 32);
    return { ok: true, kind, sandbox: true, digest: dig, reason: 'js-dag-sandbox' };
  }
  return { ok: false, reason: 'unsupported-kind', kind };
}

function runtimeInvokePlan(modulePlan, args = [], opts = {}) {
  if (!modulePlan?.ok) return { ok: false, reason: modulePlan?.reason || 'no-module' };
  return {
    ok: true, reason: 'invoke-ready', digest: modulePlan.digest, kind: modulePlan.kind || 'wasm',
    args: (args || []).slice(0, 16), exportName: opts.export || modulePlan.exportHint || 'main',
  };
}

/** SkyDrive DFS over RS/Vault/Blossom plans */
function skyDrivePutPlan(pathStr, blob, opts = {}) {
  const p = String(pathStr || '/').replace(/\\/g, '/');
  if (!p.startsWith('/')) return { ok: false, reason: 'path-must-absolute' };
  const buf = Buffer.isBuffer(blob) ? blob : Buffer.from(String(blob ?? ''), 'utf8');
  const sealed = vaultSeal(buf, opts.prev || null, { k: opts.k ?? 2, n: opts.n ?? 4 });
  const leaf = hex(sha256(Buffer.from(p + ':' + sealed.digest))).slice(0, 40);
  return {
    ok: true, path: p, digest: sealed.digest, merkleRoot: sealed.merkleRoot, leaf,
    size: buf.length, plan: sealed.plan, seq: sealed.seq, sharesB64: sealed.plan.sharesB64,
  };
}

function skyDriveGetPlan(meta, shares, opts = {}) {
  if (!meta?.digest && !meta?.plan) return { ok: false, reason: 'no-meta' };
  const open = vaultOpen(shares || meta.sharesB64 || [], meta.plan || meta, opts);
  if (!open.ok) return open;
  return { ok: true, path: meta.path, blob: open.blob, digest: meta.digest, reason: 'skydrive-open' };
}

function skyDriveHealPlan(meta, availableShares, opts = {}) {
  const need = meta?.plan?.k || meta?.k || 2;
  const have = (availableShares || []).filter(Boolean).length;
  if (have >= need) return { heal: false, reason: 'enough-shares', have, need };
  return { heal: true, reason: 'need-reshard', have, need, path: meta?.path || null };
}

/** DID — did:sky:… */
function didDocumentPlan(parts = {}) {
  const id = parts.id || ('did:sky:' + (parts.handle || parts.npub || parts.pubkey || 'anon').replace(/^did:sky:/, ''));
  const core = {
    v: 1, id,
    handle: parts.handle || null,
    pubkey: parts.pubkey || parts.npub || null,
    callingCard: parts.callingCard || null,
    passport: parts.passportDigest || parts.passport || null,
    flockId: parts.flockId || null,
  };
  const digest = hex(sha256(Buffer.from('did:sky:v1:' + xiCanon(core)))).slice(0, 40);
  return { ...core, digest, ts: parts.ts || Date.now() };
}

function didResolve(docOrId, registry = {}, opts = {}) {
  if (typeof docOrId === 'object' && docOrId?.id) return { ok: true, doc: docOrId, reason: 'inline' };
  const id = String(docOrId || '');
  const doc = registry[id] || opts.fallback || null;
  if (!doc) return { ok: false, reason: 'did-not-found', id };
  return { ok: true, doc, reason: 'resolved' };
}

function didAuthGate(doc, challenge, opts = {}) {
  if (!doc?.id || !doc?.digest) return { ok: false, reason: 'no-did' };
  if (opts.requireCard && !doc.callingCard) return { ok: false, reason: 'did-card-required' };
  const authMsg = Buffer.from('did:auth:' + doc.digest + ':' + (challenge || ''));
  const expect = hex(sha256(authMsg)).slice(0, 24);
  if (opts.sig && doc.pubkey) {
    try {
      if (!schnorrVerify(unhex(String(opts.sig)), sha256(authMsg), String(doc.pubkey))) {
        return { ok: false, reason: 'did-sig-fail' };
      }
    } catch {
      return { ok: false, reason: 'did-sig-fail' };
    }
  } else if (opts.requireSig) {
    return { ok: false, reason: 'did-sig-required' };
  }
  if (opts.proof && opts.proof !== expect && !opts.skipProof) {
    return { ok: false, reason: 'did-auth-fail' };
  }
  return { ok: true, reason: 'did-auth-ok', proof: expect, id: doc.id };
}

/** Gateway HTTP ↔ Lattice */
function httpLatticeGatewayPlan(reqPath, opts = {}) {
  const raw = String(reqPath || '/');
  if (raw.startsWith('sky://')) {
    const rest = raw.slice(6);
    const [handle, ...segs] = rest.split('/');
    return { ok: true, mode: 'sky-uri', handle, latticePath: '/' + segs.join('/'), apiPrefix: opts.apiPrefix || '/api' };
  }
  if (raw.startsWith('/.well-known/sky/')) {
    return { ok: true, mode: 'well-known', latticePath: raw.replace('/.well-known/sky', '') || '/', handle: opts.handle || null };
  }
  if (raw.startsWith('/api/') || raw.startsWith(opts.apiPrefix || '/api')) {
    return { ok: true, mode: 'omega-api', latticePath: raw, handle: opts.handle || null };
  }
  return { ok: true, mode: 'static-or-nsite', latticePath: raw, handle: opts.handle || null };
}

function gatewayRewrite(plan, opts = {}) {
  if (!plan?.ok) return { ok: false, reason: plan?.reason || 'bad-plan' };
  const prefix = opts.apiPrefix || plan.apiPrefix || '/api';
  let url = plan.latticePath || '/';
  if (plan.mode === 'sky-uri' && plan.handle) {
    url = `${prefix}/resolve/` + encodeURIComponent(plan.handle) + (plan.latticePath === '/' ? '' : plan.latticePath);
  }
  return { ok: true, url, mode: plan.mode, handle: plan.handle || null };
}

/** ADIS Cognitive Kernel — deterministic embeddings */
function embedLattice(text, opts = {}) {
  const dim = opts.dim ?? SIOS.embedDim ?? 32;
  const s = String(text ?? '');
  const vec = new Array(dim).fill(0);
  for (let i = 0; i < s.length; i++) {
    const h = sha256(Buffer.from('emb:' + i + ':' + s.charCodeAt(i) + ':' + s.slice(Math.max(0, i - 3), i + 4)));
    for (let d = 0; d < dim; d++) {
      const b = h[d % h.length];
      vec[d] += ((b & 1) ? 1 : -1) * (1 + (b >> 1) / 128);
    }
  }
  let norm = Math.sqrt(vec.reduce((a, x) => a + x * x, 0)) || 1;
  for (let d = 0; d < dim; d++) vec[d] = +(vec[d] / norm).toFixed(6);
  const digest = hex(sha256(Buffer.from('vec:' + vec.join(',')))).slice(0, 32);
  return { v: 1, dim, vec, digest };
}

function cosineSim(a, b) {
  const va = a?.vec || a, vb = b?.vec || b;
  if (!va || !vb || va.length !== vb.length) return 0;
  let dot = 0, na = 0, nb = 0;
  for (let i = 0; i < va.length; i++) { dot += va[i] * vb[i]; na += va[i] * va[i]; nb += vb[i] * vb[i]; }
  const d = Math.sqrt(na) * Math.sqrt(nb);
  return d ? +(dot / d).toFixed(6) : 0;
}

function vectorLatticePutPlan(key, textOrVec, opts = {}) {
  const emb = textOrVec?.vec ? textOrVec : embedLattice(textOrVec, opts);
  return { ok: true, ns: 'adis:vec', key: String(key), emb, meta: opts.meta || null, ts: opts.now || Date.now() };
}

function vectorLatticeQueryPlan(query, store, opts = {}) {
  const q = query?.vec ? query : embedLattice(query, opts);
  const entries = Object.entries(store || {}).map(([k, v]) => ({
    key: k, score: cosineSim(q, v.emb || v), emb: v.emb || v, meta: v.meta,
  })).sort((a, b) => b.score - a.score);
  const topK = opts.topK ?? 5;
  return { ok: true, query: q.digest, hits: entries.slice(0, topK), top: entries[0] || null };
}

function adisRecallPlan(query, store, opts = {}) {
  const q = vectorLatticeQueryPlan(query, store, opts);
  const formula = opts.formula || 'x';
  let logic = null;
  try { logic = new FormulaVM().eval(formula, { x: q.top?.score || 0, n: q.hits.length }); }
  catch { logic = q.top?.score || 0; }
  return { ok: true, recall: q, logic, reason: q.top ? 'hit' : 'cold' };
}

/** DTA tools */
function dtaToolPlan(task, opts = {}) {
  const canon = typeof task === 'string' ? task : xiCanon(task);
  const id = hex(sha256(Buffer.from('dta:tool:v1:' + canon))).slice(0, 24);
  const kind = opts.kind || 'nabla';
  const body = opts.body || {
    nodes: { in: { value: { $ref: 'input' } }, out: { op: 'formula', args: { expr: 'x*1', vars: { x: { $ref: 'in' } } } } },
    output: 'out',
  };
  return { v: 1, id, kind, task: canon.slice(0, 200), body, digest: hex(sha256(Buffer.from(xiCanon({ id, kind, body })))).slice(0, 32) };
}

function dtaPublishPlan(tool, opts = {}) {
  if (!tool?.id) return { ok: false, reason: 'no-tool' };
  return { ok: true, key: 'adis:tool:' + tool.id, tool, ts: opts.now || Date.now() };
}

function dtaInvokePlan(tool, input, opts = {}) {
  if (!tool?.body && !tool?.digest) return { ok: false, reason: 'no-tool' };
  try {
    if (tool.kind === 'formula' && typeof tool.body === 'string') {
      const out = new FormulaVM().eval(tool.body, { x: Number(input) || 0, ...(opts.vars || {}) });
      return { ok: true, output: out, toolId: tool.id, reason: 'formula' };
    }
    if (tool.kind === 'nabla' || tool.body?.nodes) {
      let output = input;
      try {
        if (typeof xiClientEval === 'function') {
          const ev = xiClientEval(tool.body, { input, ...(typeof input === 'object' && input ? input : {}) });
          output = ev?.output ?? output;
        } else if (typeof xiDagEval === 'function') {
          output = xiDagEval(tool.body, { input })?.output ?? output;
        }
      } catch { /* echo */ }
      return { ok: true, output, toolId: tool.id, reason: 'nabla' };
    }
    return { ok: true, output: input, toolId: tool.id, reason: 'echo' };
  } catch (err) {
    return { ok: false, reason: err.message || 'invoke-fail', toolId: tool.id };
  }
}

// ═══════════ Swarm Mesh S173–S180 — multi-agent apps on same SYNC_ID ═══════════

function swarmNodeManifestPlan(parts = {}) {
  const appId = parts.appId || hex(sha256(Buffer.from('swarm:app:' + (parts.appName || parts.appId || 'default')))).slice(0, 16);
  const agentId = parts.agentId || hex(sha256(Buffer.from('swarm:agent:' + (parts.syncId || 'local') + ':' + appId + ':' + (parts.role || 'worker')))).slice(0, 20);
  return {
    v: 1, agentId, appId,
    deviceId: parts.deviceId || null,
    role: parts.role || 'worker',
    capabilities: (parts.capabilities || []).map(String),
    modelProfile: parts.modelProfile || 'default',
    description: String(parts.description || ''),
    pubkey: parts.pubkey || null,
    status: parts.status || 'idle',
    ts: parts.ts || Date.now(),
    ttlMs: parts.ttlMs ?? SWARM_MESH.heartbeatMs * 3,
  };
}

function swarmRegisterGate(manifest, registry = [], opts = {}) {
  if (!SWARM_MESH.enabled && !opts.force) return { ok: false, reason: 'swarm-disabled' };
  if (!manifest?.agentId) return { ok: false, reason: 'bad-manifest' };
  const now = opts.now || Date.now();
  const ttl = manifest.ttlMs ?? SWARM_MESH.heartbeatMs * 3;
  const alive = (registry || []).filter((a) => a?.agentId && now - (a.ts || 0) < (a.ttlMs ?? ttl));
  const max = opts.maxAgents ?? SWARM_MESH.maxAgents ?? 10_000;
  if (alive.length >= max && !alive.some((a) => a.agentId === manifest.agentId)) {
    return { ok: false, reason: 'quota-exceeded', have: alive.length, max };
  }
  return { ok: true, reason: 'registered', agentId: manifest.agentId, registryKey: 'swarm:agent:' + manifest.agentId };
}

function swarmDiscoverPlan(registry, query = {}, opts = {}) {
  const now = opts.now || Date.now();
  const cap = String(query.capability || query.cap || '').toLowerCase();
  const role = String(query.role || '').toLowerCase();
  let nodes = (registry || []).filter((a) => a?.agentId && now - (a.ts || 0) < (a.ttlMs ?? SWARM_MESH.heartbeatMs * 3));
  if (role) nodes = nodes.filter((a) => String(a.role || '').toLowerCase() === role);
  if (cap) nodes = nodes.filter((a) => (a.capabilities || []).some((c) => String(c).toLowerCase().includes(cap)));
  const qText = query.query || query.q || '';
  if (qText && nodes.length > 1) {
    const qEmb = embedLattice(String(qText));
    nodes = nodes.map((n) => {
      const desc = embedLattice((n.description || '') + ' ' + (n.capabilities || []).join(' '));
      return { ...n, score: cosineSim(qEmb, desc) };
    }).sort((a, b) => (b.score || 0) - (a.score || 0));
  }
  return { ok: true, nodes: nodes.slice(0, opts.limit ?? 32), total: nodes.length };
}

function swarmHandoffPlan(session, fromAgent, toAgent, opts = {}) {
  if (!session?.sessionId) return { ok: false, reason: 'no-session' };
  if (!toAgent?.agentId) return { ok: false, reason: 'no-target' };
  const history = (session.history || []).slice(-(opts.maxHistory ?? 40));
  const ts = opts.now || Date.now();
  const handoff = {
    v: 1, sessionId: session.sessionId,
    from: fromAgent?.agentId || fromAgent || null,
    to: toAgent.agentId,
    reason: opts.reason || 'capability-handoff',
    history, partial: opts.partial ?? null,
    ts, ttlMs: opts.ttlMs ?? SWARM_MESH.handoffTtlMs,
    digest: hex(sha256(Buffer.from('swarm:handoff:' + session.sessionId + ':' + toAgent.agentId + ':' + ts))).slice(0, 32),
  };
  return { ok: true, handoff, key: 'swarm:handoff:' + handoff.digest };
}

function swarmSynthesizePlan(answers = [], opts = {}) {
  const list = (answers || []).filter((a) => a && (a.content != null || a.output != null));
  if (!list.length) return { ok: false, reason: 'no-answers', content: null };
  if (list.length === 1) {
    return { ok: true, content: String(list[0].content ?? list[0].output), nSources: 1, reason: 'single' };
  }
  const fusion = unifiedMindFusionPlan(list.map((a) => ({
    content: String(a.content ?? a.output ?? ''),
    weight: a.weight ?? 1,
    lobe: a.agentId || 'agent',
  })), opts);
  return { ok: !!fusion.ok, content: fusion.content, digest: fusion.digest, nSources: list.length, reason: 'swarm-synthesize' };
}

function swarmSessionDigestPlan(sessionId, steps = [], opts = {}) {
  const sid = String(sessionId || '');
  const canon = xiCanon({ sessionId: sid, steps: (steps || []).map((s) => ({ agent: s.agentId, op: s.op, digest: s.digest })) });
  return {
    ok: !!sid,
    sessionId: sid,
    digest: hex(sha256(Buffer.from('swarm:session:v1:' + canon))).slice(0, 40),
    steps: (steps || []).length,
    ts: opts.now || Date.now(),
  };
}

function toolLibrarySearchPlan(query, tools = [], opts = {}) {
  const q = String(query || '');
  const qEmb = embedLattice(q);
  const scored = (tools || []).map((t) => {
    const desc = embedLattice((t.description || t.task || '') + ' ' + (t.id || ''));
    return { tool: t, score: cosineSim(qEmb, desc) };
  }).filter((x) => x.score > (opts.minScore ?? 0.05)).sort((a, b) => b.score - a.score);
  return { ok: true, hits: scored.slice(0, opts.topK ?? SWARM_MESH.contextPruneTopK ?? 12), query: q };
}

function toolLibraryLazyInjectPlan(tools = [], opts = {}) {
  const sigs = (tools || []).map((t) => ({
    id: t.id,
    name: t.name || t.id,
    kind: t.kind || 'nabla',
    params: t.params || ['input'],
    digest: t.digest,
    version: t.version || 1,
  }));
  return { ok: true, signatures: sigs, lazy: opts.lazy ?? SWARM_MESH.toolSigOnlyInPrompt !== false, reason: 'lazy-inject' };
}

function toolLibraryDeprecatePlan(tools = [], opts = {}) {
  const now = opts.now || Date.now();
  const maxAge = opts.maxAgeMs ?? SWARM_MESH.toolDeprecateMs ?? 30 * 86_400_000;
  const deprecated = [];
  const kept = [];
  const byDigest = new Map();
  for (const t of tools || []) {
    const lastUsed = t.lastUsed || t.ts || 0;
    if (now - lastUsed > maxAge && !t.pinned) { deprecated.push(t); continue; }
    const d = t.digest || t.id;
    if (byDigest.has(d)) deprecated.push(t);
    else { byDigest.set(d, t); kept.push(t); }
  }
  return { ok: true, kept, deprecated, merged: (tools || []).length - kept.length };
}

function dtaPublishSwarmPlan(tool, opts = {}) {
  const pub = dtaPublishPlan(tool, opts);
  if (!pub.ok) return pub;
  const sig = toolLibraryLazyInjectPlan([{ ...tool, name: tool.id }]).signatures[0];
  return {
    ...pub,
    swarmKey: 'swarm:tool:' + tool.id,
    version: opts.version || tool.version || 1,
    sig,
    deprecateAfter: opts.deprecateAfter || (Date.now() + (SWARM_MESH.toolDeprecateMs || 30 * 86_400_000)),
  };
}

/** Unified mind fusion — kernel + SLM + APIs = one brain */
function unifiedMindFusionPlan(sources, opts = {}) {
  const list = (sources || []).filter((s) => s && (s.content != null || s.vec || s.emb));
  if (!list.length) return { ok: false, reason: 'no-sources', content: null, digest: null };
  const embs = list.map((s) => s.emb || s.vec || embedLattice(String(s.content || '')));
  const dim = embs[0].vec?.length || embs[0].length || SIOS.embedDim;
  const fused = new Array(dim).fill(0);
  let wsum = 0;
  for (let i = 0; i < list.length; i++) {
    const w = list[i].weight ?? 1;
    const v = embs[i].vec || embs[i];
    wsum += w;
    for (let d = 0; d < dim; d++) fused[d] += (v[d] || 0) * w;
  }
  for (let d = 0; d < dim; d++) fused[d] = +((fused[d] / (wsum || 1))).toFixed(6);
  const contents = list.map((s) => String(s.content || '')).filter(Boolean);
  const content = contents.sort((a, b) => b.length - a.length)[0] || contents[0] || '';
  const digest = hex(sha256(Buffer.from('fusion:v1:' + fused.join(',') + ':' + content.slice(0, 80)))).slice(0, 40);
  return {
    ok: true, content, emb: { v: 1, dim, vec: fused, digest }, digest,
    nSources: list.length, lobes: list.map((s) => s.lobe || s.source || 'x').slice(0, 32),
    reason: 'unified-flock-mind',
  };
}

/** SLM plugin plans */
function slmManifestPlan(parts = {}) {
  const core = {
    v: 1,
    name: parts.name || 'adis-slm-tiny',
    bytes: parts.bytes || 0,
    hash: parts.hash || null,
    format: parts.format || 'fixture',
    url: parts.url || parts.manifestUrl || null,
  };
  const digest = hex(sha256(Buffer.from('slm:man:v1:' + xiCanon(core)))).slice(0, 32);
  return { ...core, digest, ok: true };
}

function slmBootstrapPlan(manifest, opts = {}) {
  if (!manifest?.ok && !manifest?.digest) return { ok: false, reason: 'no-manifest' };
  const mb = (manifest.bytes || 0) / (1024 * 1024);
  const maxMb = opts.maxLocalMb ?? ADIS.maxLocalMb ?? 512;
  if (mb > maxMb && !opts.force) return { ok: false, reason: 'slm-too-large', mb, maxMb };
  return {
    ok: true, reason: 'bootstrap-ready', manifest, preferMesh: opts.preferMeshInfer ?? ADIS.preferMeshInfer !== false,
    allowLocal: opts.allowLocal ?? ADIS.allowLocal !== false, capability: 'slm:' + (manifest.name || 'tiny'),
  };
}

function slmInferPlan(prompt, opts = {}) {
  const mesh = !!(opts.preferMesh ?? ADIS.preferMeshInfer);
  const workers = opts.workersAlive ?? 0;
  if (mesh && workers > 0) return { ok: true, mode: 'mesh', reason: 'mesh-infer', promptDigest: mindPromptDigest?.(prompt) || hex(sha256(Buffer.from(String(prompt)))).slice(0, 24) };
  if (opts.allowLocal !== false && (opts.hasWeights || opts.fixture)) {
    // fixture cognitive echo — real SLM weights optional
    const content = opts.fixtureResponse || ('[ADIS-SLM] ' + String(prompt || '').slice(0, 400));
    return { ok: true, mode: 'local-fixture', reason: 'local-or-fixture', content, emb: embedLattice(content) };
  }
  return { ok: false, reason: 'no-infer-capacity', mode: 'none' };
}

function loraDeltaPlan(baseWeights, delta, opts = {}) {
  const base = baseWeights || {};
  const d = delta || {};
  const keys = [...new Set([...Object.keys(base), ...Object.keys(d)])];
  const out = {};
  const scale = opts.scale ?? 1;
  for (const k of keys) out[k] = +(((base[k] || 0) + scale * (d[k] || 0))).toFixed(6);
  const digest = hex(sha256(Buffer.from('lora:' + xiCanon(out)))).slice(0, 24);
  return { ok: true, weights: out, digest, n: keys.length };
}

function adisTracePlan(event, opts = {}) {
  const e = {
    v: 1,
    type: event?.type || 'tick',
    msg: String(event?.msg || event?.message || '').slice(0, 200),
    flockId: event?.flockId || opts.flockId || null,
    pathClass: event?.pathClass || null,
    ts: event?.ts || opts.now || Date.now(),
  };
  e.line = `${e.ts.toString(16)} ${e.type} ${e.msg}`.slice(0, 120);
  e.digest = hex(sha256(Buffer.from(xiCanon(e)))).slice(0, 16);
  return e;
}

function siosPluginManifest(opts = {}) {
  const plugs = { ...SIOS.plugins, ...(opts.plugins || {}) };
  return {
    v: 1, os: 'sios', microKernel: true,
    plugins: plugs,
    trustServer: false,
    note: 'Sovereign Internet OS — plugins over Lattice; not a legal state / not a botnet',
    ts: opts.now || Date.now(),
  };
}

/** S56: רווחי עובד = סכום קבלות × מכפיל SLA */
function workerEarningsPlan(paidRows, sla, opts = {}) {
  const score = sla?.score ?? opts.score ?? 50;
  const mult = Math.max(0.25, Math.min(1.5, score / 70));
  const by = {};
  for (const row of paidRows || []) {
    const w = row.worker || 'unknown';
    by[w] ??= { worker: w, n: 0, grossUsd: 0, digests: [] };
    by[w].n++;
    by[w].grossUsd += +(row.price?.usd || row.usd || 0);
    if (row.digest) by[w].digests.push(row.digest);
  }
  return Object.values(by).map((e) => ({
    worker: e.worker,
    n: e.n,
    grossUsd: +e.grossUsd.toFixed(6),
    usd: +(e.grossUsd * mult).toFixed(6),
    slaMult: +mult.toFixed(3),
    slaScore: score,
    digests: e.digests.slice(0, 20),
  })).sort((a, b) => b.usd - a.usd);
}

/** S57: תוכנית שיקוף seed בין מבקרים */
function seedGossipPlan(localSeed, peers = [], opts = {}) {
  const tips = [];
  if (localSeed?.url) tips.push({ url: localSeed.url, hash: localSeed.hash, source: 'local', ts: localSeed.ts || Date.now() });
  for (const p of peers || []) {
    if (p?.url && (!localSeed?.url || p.url !== localSeed.url)) {
      tips.push({ url: p.url, hash: p.hash || null, source: p.source || 'peer', ts: p.ts || Date.now() });
    }
  }
  // דטרמיניסטי: hash ראשון לקסיקוגרפי מנצח כ-primary
  tips.sort((a, b) => String(a.hash || a.url).localeCompare(String(b.hash || b.url)));
  return {
    primary: tips[0] || null,
    mirrors: tips.slice(0, opts.limit || 8),
    n: tips.length,
    note: 'Browsers fetch Blossom hash; no Node required',
  };
}

/** S58: סיבוב שורש אמון — old+new חיים עד קוורום על החדש */
function trustRootRotatePlan(oldRoot, newRoot, attestations, opts = {}) {
  const k = opts.quorum || 2;
  const qNew = trustMeshQuorum((attestations || []).filter((a) => a.digest === newRoot), k);
  const qOld = trustMeshQuorum((attestations || []).filter((a) => a.digest === oldRoot), k);
  return {
    phase: qNew.ok ? 'cutover' : 'dual-root',
    oldRoot, newRoot,
    quorumNew: qNew, quorumOld: qOld,
    active: qNew.ok ? [newRoot] : [oldRoot, newRoot].filter(Boolean),
    downtime: false,
    note: 'Old root stays valid until new reaches quorum',
  };
}

/** S55: פריט תור continuum */
function mindContinuumItem(input, opts = {}) {
  const messages = mindNormalizeMessages(input, opts.system);
  const digest = mindPromptDigest(messages, opts.model || 'pending', { thread: opts.thread || 'default' });
  return {
    digest, input: typeof input === 'string' ? input : messages,
    opts: { thread: opts.thread, name: opts.name || 'continuum', system: opts.system },
    ts: opts.ts || Date.now(), status: 'queued',
  };
}

/** S64: promote רק מעל סף SLA */
function slaPromoteGate(sla, opts = {}) {
  const min = opts.minSla ?? 50;
  const score = typeof sla?.score === 'number' ? sla.score : 0;
  return { ok: score >= min, score, min, reason: score >= min ? 'sla-ok' : 'sla-below-promote' };
}

/** S62/S68: unpack רק אם tip תחת active trust roots — skip אסור בלי allowSkipTrustRoot */
function trustRootCapsuleGate(tip, activeRoots, opts = {}) {
  const roots = (activeRoots || []).filter(Boolean);
  if (opts.skipTrustRoot) {
    if (opts.allowSkipTrustRoot || !roots.length) return { ok: true, reason: roots.length ? 'skipped' : 'no-roots-configured' };
    return { ok: false, reason: 'skip-trust-root-forbidden', activeRoots: roots };
  }
  if (!roots.length) return { ok: true, reason: 'no-roots-configured' };
  const tipRoot = tip?.trustRoot || tip?.root || null;
  const digest = tip?.digest || null;
  if (digest && roots.includes(digest)) return { ok: true, reason: 'digest-is-root' };
  if (tipRoot && roots.includes(tipRoot)) return { ok: true, reason: 'tip-root-active' };
  return { ok: false, reason: 'trust-root-pin', activeRoots: roots, tipRoot, digest };
}

/** S67: promote חסום בזמן dual-root אלא אם tip תחת active */
function dualRootPromoteGate(trustState, tip, opts = {}) {
  const phase = trustState?.phase;
  const active = (trustState?.active || []).filter(Boolean);
  if (phase !== 'dual-root') return { ok: true, reason: 'not-dual' };
  if (opts.allowDualPromote) return { ok: true, reason: 'allowed' };
  const tipRoot = tip?.trustRoot || tip?.root || null;
  if (tipRoot && active.includes(tipRoot)) return { ok: true, reason: 'tip-in-dual-active' };
  if (tip?.digest && active.includes(tip.digest)) return { ok: true, reason: 'digest-is-active-root' };
  return { ok: false, reason: 'dual-root-promote-blocked', active, tipRoot };
}

/** S71: promote חסום אם יש mind:paid לא מסולק ל-canary */
function promoteEarningsHoldGate(paidRows, canaryDigest, opts = {}) {
  const rows = (paidRows || []).map((r) => r.value || r);
  const hold = rows.filter((v) => {
    if (!v || v.settled || v.paidOut || v.escrow === 'forfeited') return false;
    if (v.escrow === 'canary') return true;
    if (canaryDigest && (v.digest === canaryDigest || v.canaryDigest === canaryDigest)) return true;
    return false;
  });
  return { ok: hold.length === 0 || !!opts.allowUnsettledPromote, reason: hold.length ? 'promote-earnings-hold' : 'clear', n: hold.length };
}

/** S72: escrow לרווחי canary */
function canaryEarningsEscrowPlan(row, stage) {
  const base = { ...(row || {}) };
  if (stage === 'canary') return { ...base, escrow: 'canary', escrowHold: true };
  if (stage === 'full' || stage === 'released') return { ...base, escrow: 'released', escrowHold: false };
  if (stage === 'rolled-back' || stage === 'forfeited') return { ...base, escrow: 'forfeited', escrowHold: false, paidOut: base.paidOut || 'forfeit' };
  return base;
}

/** S74: tip קפסולה חייב כתובת/handle */
function addressBoundCapsuleGate(tipOrAddr, opts = {}) {
  const handle = tipOrAddr?.handle || tipOrAddr?.sky || tipOrAddr?.skyHandle || null;
  const canon = tipOrAddr?.canon || tipOrAddr?.canonical || null;
  const domain = tipOrAddr?.domain || null;
  if (opts.allowUnaddressed) return { ok: true, reason: 'skipped' };
  if (!handle && !canon && !domain) return { ok: false, reason: 'capsule-tip-unaddressed' };
  return { ok: true, handle, canon, domain };
}

/** S83: אין תשלום על תשובות zero-origin/cache/distill */
function zeroOriginPayGate(source, opts = {}) {
  const s = String(source || '');
  if (opts.allowOfflinePay) return { ok: true, reason: 'allowed' };
  if (/zero-origin|cache|distill|offline/i.test(s)) return { ok: false, reason: 'offline-pay-forbidden', source: s };
  return { ok: true, source: s };
}

/** S84: seed רק עם distill מאומת בקוורום */
function verifiedSeedGate(verTip, opts = {}) {
  if (opts.allowUnverified) return { ok: true, reason: 'skipped' };
  if (!verTip?.digest && !verTip?.dagId) return { ok: false, reason: 'seed-unverified-distill' };
  const min = opts.minAgree ?? 2;
  const agree = verTip.agree ?? (verTip.quorumOk ? min : 0);
  if (agree < min) return { ok: false, reason: 'seed-unverified-distill', agree, min };
  return { ok: true, digest: verTip.digest || null, agree };
}

/** S80: continuum CDN רק אחרי distill+quorum */
function continuumQuorumGate(distilled) {
  if (!distilled) return { ok: false, reason: 'no-distill', requeue: true };
  if (distilled.ok === false && distilled.reason === 'quorum-pending') {
    return { ok: false, reason: 'continuum-cdn-without-quorum', requeue: true };
  }
  if (!distilled.ok) return { ok: false, reason: distilled.reason || 'distill-fail', requeue: !!distilled.reason };
  return { ok: true };
}

/** S79: auto-promote ב-heal רק אחרי N מחזורי SLA תקינים */
function autonomicPromoteHealPlan(meta, slaGate, streak, opts = {}) {
  const need = opts.needStreak ?? 3;
  if (meta?.stage !== 'canary') return { promote: false, streak: 0, reason: 'not-canary' };
  if (!slaGate?.ok) return { promote: false, streak: 0, reason: 'sla-fail' };
  const next = (streak || 0) + 1;
  if (next < need) return { promote: false, streak: next, need, reason: 'heal-auto-promote-blocked' };
  return { promote: true, streak: next, need, reason: 'streak-ok' };
}

/** S81: אימוץ קפסולה זרה דורש federation tip pinned לאותו pub */
function federationCapsulePinGate(fed, foreignPub) {
  if (!fed || !fed.foreignPub) return { ok: false, reason: 'capsule-adopt-without-fed-pin' };
  if (foreignPub && fed.foreignPub !== foreignPub) return { ok: false, reason: 'capsule-adopt-wrong-fed' };
  if (!fed.pinned) return { ok: false, reason: 'capsule-adopt-without-fed-pin' };
  return { ok: true, foreignPub: fed.foreignPub, digest: fed.digest || null };
}

/** S76: לפני תמחור/תשלום — shadow ∇ חייב לעבור */
function shadowPricedMindGate(content, policyOutput, opts = {}) {
  if (opts.skipShadow) return { ok: true, reason: 'skipped', output: content };
  const g = mindShadowGate(content, policyOutput == null ? 1 : policyOutput);
  if (!g.ok) return { ok: false, reason: 'shadow-deny-before-price', gate: g };
  return { ok: true, output: g.output != null ? g.output : content, gate: g };
}

/** S59: גשר סליקה→earnings */
function settlementEarningsBridgePlan(settlement, earnings, opts = {}) {
  return {
    v: 1,
    settlementDigest: settlement?.digest || null,
    earningsDigest: earnings?.digest || null,
    usd: settlement?.usd ?? null,
    nWorkers: (earnings?.earnings || earnings || []).length || 0,
    bridged: !!(settlement?.digest && earnings?.digest),
    ts: opts.now || Date.now(),
  };
}

/** S66: האם heal צריך לסובב trust root */
function autonomicTrustHealPlan(rotation, opts = {}) {
  const stableMs = opts.stableMs ?? 30_000;
  if (!rotation?.newRoot) return { rotate: false, reason: 'no-rotation' };
  if (rotation.phase === 'cutover') return { rotate: false, reason: 'already-cutover' };
  const q = rotation.quorumNew || rotation.attestNew?.quorum;
  if (!q?.ok) return { rotate: false, reason: 'quorum-pending' };
  const age = Date.now() - (rotation.ts || 0);
  if (age < stableMs) return { rotate: false, reason: 'not-stable-yet', age, stableMs };
  return { rotate: true, reason: 'stable-quorum', newRoot: rotation.newRoot, age };
}

/** S63: חימום CDN מ־gossip primary */
function gossipCdnWarmPlan(lattice, primary, opts = {}) {
  const mirrors = [...(lattice?.mirrors || [])];
  if (primary?.url && !mirrors.some((m) => m.url === primary.url)) {
    mirrors.unshift({ kind: 'visitor-seed', url: primary.url, hash: primary.hash || null, warmed: true });
  }
  return {
    v: 1,
    mirrors,
    primary: primary || null,
    warmed: !!primary?.url,
    canon: lattice?.canon || null,
    ts: opts.now || Date.now(),
  };
}

/** הוראות 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 BOOT_MIRRORS = ${JSON.stringify(PUBLIC_WEBSITE ? mirrorAddresses() : [])};
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 isPlainObj(v){ return !!v && typeof v==='object' && !Array.isArray(v) && Object.getPrototypeOf(v)===Object.prototype; }
function smartMergeVal(a,b,preferB){
  if(a==null) return b; if(b==null) return a;
  try{ if(JSON.stringify(a)===JSON.stringify(b)) return a; }catch(e){}
  if(isPlainObj(a)&&isPlainObj(b)){
    const o={}; const ks=Object.keys(a).concat(Object.keys(b));
    const seen={};
    for(let i=0;i<ks.length;i++){
      const k=ks[i]; if(seen[k]) continue; seen[k]=1;
      const ha=Object.prototype.hasOwnProperty.call(a,k), hb=Object.prototype.hasOwnProperty.call(b,k);
      if(ha&&!hb) o[k]=a[k]; else if(!ha&&hb) o[k]=b[k];
      else if(isPlainObj(a[k])&&isPlainObj(b[k])) o[k]=smartMergeVal(a[k],b[k],preferB);
      else { try{ o[k]=JSON.stringify(a[k])===JSON.stringify(b[k])?a[k]:(preferB?b[k]:a[k]); }catch(e){ o[k]=preferB?b[k]:a[k]; } }
    }
    return o;
  }
  return preferB?b:a;
}
function mergeEntrySmart(cur, entry){
  if(!cur) return entry;
  if(cur.ts>entry.ts && !isPlainObj(cur.v)) return cur;
  const preferB = entry.ts >= cur.ts;
  const older = preferB ? cur.v : entry.v;
  const newer = preferB ? entry.v : cur.v;
  const v = (isPlainObj(older)||isPlainObj(newer)) ? smartMergeVal(older, newer, true) : (preferB?entry.v:cur.v);
  return { v:v, ts: Math.max(cur.ts||0, entry.ts||0) };
}

/* ─── מיזוג אירועים מהרשת: נתונים / משתמשים / אחסון פרטי / חישוב ─── */
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];
    const next = mergeEntrySmart(c, entry);
    if (c && next.ts === c.ts) { try { if (JSON.stringify(next.v) === JSON.stringify(c.v)) return; } catch (e) {} }
    kv[key] = next;
    entry = next;
    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];
    userData[k] = mergeEntrySmart(c, 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); } };
    /* S100: snapshot ישן מדי → force live mesh (בלי fallback מוקדם) */
    const maxAge = init.snapshotMaxAgeMs || 120000;
    const snapFresh = (() => {
      const s = snaps[sh]; if (!s) return false;
      const age = s.ts ? (Date.now() - s.ts) : Infinity;
      return age <= maxAge;
    })();
    const forceLive = init.forceLiveMesh || (snaps[sh] && !snapFresh);
    /* מנצח: עובד-שרת חי אם ענה מהר; אחרת נופלים לתמונה הסטטית מהרשת (ללא-מפעיל) */
    const hard = setTimeout(() => {
      if (done) return; done = true; delete apiWaiters[id];
      if (snaps[sh] && (snapFresh || !forceLive || init.allowStaleSnapshot)) resolve(snaps[sh]);
      else if (snaps[sh] && !snapFresh) reject(new Error('SkyCloud: snapshot stale — no live mesh for ' + path));
      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] && snapFresh && !forceLive) 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); },

  /* מראות נגד חסימת שער — אותו אתר בכמה דומיינים; לשתף את הרשימה לא קישור יחיד */
  mirrors: async () => {
    try { const r = await fetch('/access.json', { cache: 'no-store' }); if (r.ok) { const j = await r.json(); if (j.mirrors && j.mirrors.length) return j.mirrors; } } catch (e) {}
    try { const r = await fetch('/skylive.json', { cache: 'no-store' }); if (r.ok) { const j = await r.json(); if (j.mirrors && j.mirrors.length) return j.mirrors; } } catch (e) {}
    return BOOT_MIRRORS.slice();
  },
  accessPack: async () => {
    try { const r = await fetch('/access.json', { cache: 'no-store' }); if (r.ok) return r.json(); } catch (e) {}
    const m = await window.SkyCloud.mirrors();
    return { v: 1, primary: m[0] || location.origin, mirrors: m, accessPath: '/access.html' };
  },
  openAccessPage: () => { location.href = '/access.html'; },

  /* משתמשים — כמו 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,

  /* Swarm Mesh S173–S180 — multi-agent על אותו SYNC_ID */
  swarm: {
    register: (opts) => apiCall('/api/swarm/register', { method: 'POST', body: opts }),
    nodes: (q) => apiCall('/api/swarm/nodes' + (q ? '?' + new URLSearchParams(q).toString() : '')),
    ask: (input, opts) => apiCall('/api/swarm/ask', { method: 'POST', body: Object.assign({}, opts || {}, { input }) }),
    handoff: (sessionId, to, opts) => apiCall('/api/swarm/handoff', { method: 'POST', body: Object.assign({}, opts || {}, { sessionId, toAgentId: to }) }),
    synthesize: (answers, opts) => apiCall('/api/swarm/synthesize', { method: 'POST', body: Object.assign({}, opts || {}, { answers }) }),
    session: (id) => apiCall('/api/swarm/session/' + encodeURIComponent(id)),
    toolsSearch: (q, opts) => apiCall('/api/swarm/tools/search', { method: 'POST', body: Object.assign({}, opts || {}, { query: q }) }),
  },

  /* חוות השרתים המבוזרת — כל טאב וכל מכשיר הם עובדי-חישוב */
  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;
  },
  /* S31: אפס-אמון — המבקר מאמת; לא "תאמין לשרת" */
  zeroTrust: (out) => ({
    trustServer: false,
    verified: !!(out && out.verified && (out.verified.match === true || out.verified === true)),
    receiptId: out && out.receipt && out.receipt.id,
    pathDigest: out && out.trace && out.trace.pathDigest,
    contextOk: !out || out.contextOk !== false,
    source: out && out.source,
  }),
  verifyLive: async (out) => {
    const zt = window.SkyCloud.zeroTrust(out);
    if (out && out.receipt) zt.receiptOk = await window.SkyCloud.verifyReceipt(out.receipt, out.output);
    else zt.receiptOk = false;
    zt.ok = !!(zt.verified && zt.contextOk && (zt.receiptOk || !out.receipt));
    return zt;
  },
  addressStack: () => window.SkyCloud.load('address:stack'),
  sla: () => window.SkyCloud.load('phi:sla') || (window.SkyCloud.load('phi:health') && window.SkyCloud.load('phi:health').sla),
  cdnLattice: () => window.SkyCloud.load('cdn:lattice'),
};

/* Door 1+12 + S7/S18/S19: Policy-CDN + SkyHandle + proof-carrying UI + genome */
(async () => {
  try {
    await ready;
    let meta = null, site = null, handle = null;
    const el = document.getElementById('skycloud-live');
    if (el && el.getAttribute('data-skysite')) site = el.getAttribute('data-skysite');
    if (el && el.getAttribute('data-skyhandle')) handle = el.getAttribute('data-skyhandle');
    try { const r = await fetch('/skylive.json', { cache: 'no-store' }); if (r.ok) { meta = await r.json(); if (!site) site = meta.site; if (!handle) handle = meta.handle; } } catch {}
    /* Living Global Address: מבקר = עובד-Ω לנתיבים קריטיים — הכתובת חיה בלי Node ייעודי */
    if (meta && (meta.living || meta.mode === 'living-mesh') && meta.staticOnly !== true) {
      try {
        window.__skyLiving = meta;
        const livingBody = (extra) => Object.assign({
          living: true, visitorWorker: true, trustServer: false, staticOnly: false, mode: 'living-mesh', ts: Date.now(),
        }, extra || {});
        window.SkyCloud.serveApi({
          'GET /api/health': async () => livingBody({
            node: true, mesh: true, workersAlive: meta.workersAlive,
            health: meta.health || null,
            db: (window.SkyCloud.load('phi:health') && window.SkyCloud.load('phi:health').db) || null,
            from: 'visitor-worker',
          }),
          'GET /api/sla': async () => livingBody({
            sla: (window.SkyCloud.sla && window.SkyCloud.sla()) || window.SkyCloud.load('phi:sla') || { workersAlive: meta.workersAlive || 1, target: 'visitor', score: 40 },
            from: 'visitor-worker',
          }),
          'GET /api/trust': async () => livingBody({ model: 'visitor-verifies', trustRoot: meta.trustRoot || null }),
          'GET /api/address': async () => livingBody(window.SkyCloud.addressStack() || meta.livingPlan || { canon: meta.canon, sky: meta.sky, domain: meta.domain }),
          'GET /api/compose/status': async () => livingBody({ doors: ['living', 'mesh', 'nabla'], policies: meta.policies || [] }),
        });
        /* S103 Visitor Worker Mesh — כל טאב כ-worker עם beat ל-webkv */
        const beatVisitor = async () => {
          try {
            const id = 'v:' + (cur.pub || 'tab').slice(0, 16);
            const beat = { id: id, visitor: true, node: false, tab: true, opsPerSec: 0, lagMs: 0, ts: Date.now() };
            const reg = window.SkyCloud.load('mesh:workers') || {};
            reg[id] = beat;
            await window.SkyCloud.save('mesh:workers', reg);
            await window.SkyCloud.save('phi:living', { living: true, visitorBeat: id, workersAlive: Object.keys(reg).length, ts: Date.now() });
          } catch {}
        };
        beatVisitor();
        setInterval(beatVisitor, 25000);
        /* S124 Zero-Peer Degraded UI + dedicated dial */
        try {
          const dial = meta.dial || null;
          if (dial && dial.channel) await window.SkyCloud.save('mesh:dial', dial);
          const workersAlive = meta.workersAlive ?? (meta.health && meta.health.workersAlive) ?? 0;
          const deg = (workersAlive <= 0)
            ? { degraded: true, profile: 'degraded', reason: 'zero-peer-no-fresh-snap' }
            : { degraded: false, profile: 'full', reason: 'mesh-alive' };
          if (deg.degraded) {
            await window.SkyCloud.save('app:ui', { profile: 'degraded', genome: 'degraded', reason: deg.reason, ts: Date.now() });
            const liveEl = document.getElementById('skycloud-living');
            if (liveEl) liveEl.textContent = 'Degraded · אין mesh/snap טרי · ∇ בלבד';
          }
        } catch {}
        const badge = document.getElementById('skycloud-living') || document.getElementById('skycloud-trust');
        const hb = meta.health || {};
        const badgeTxt = 'Living · workers=' + (meta.workersAlive ?? hb.workersAlive ?? '?') + ' · badge=' + (hb.badge || '?') + ' · staticOnly=false';
        if (badge && badge.id === 'skycloud-living') badge.textContent = badgeTxt;
        else if (badge) badge.textContent = (badge.textContent ? badge.textContent + ' · ' : '') + badgeTxt;
      } catch {}
    }
    if (!site && !(el && el.getAttribute('data-skylive')) && !(meta && meta.living)) return;
    site = site || 'pricing';
    /* S7/S19: app:ui + genome מ-webkv מחליפים מדיניות מעטפת */
    try {
      const genome = window.SkyCloud.load('capsule:genome');
      const ui = window.SkyCloud.load('app:ui');
      const tip = (ui && ui.profile) || (genome && genome.profile) || null;
      if (tip === 'degraded' || tip === 'offline-nabla') {
        site = (meta && meta.policies && (meta.policies.includes('access') ? 'access' : meta.policies[1])) || site;
      }
      const gEl = document.getElementById('skycloud-genome');
      if (gEl) gEl.textContent = 'genome=' + ((genome && genome.profile) || tip || 'full') + (handle ? ' · sky://' + handle : '');
      const pin = window.SkyCloud.load('db:pin');
      const pEl = document.getElementById('skycloud-pin');
      if (pEl && pin) pEl.textContent = 'DB pin seq=' + (pin.seq != null ? pin.seq : '?') + (pin.digest ? ' · ' + String(pin.digest).slice(0, 12) : '');
    } catch {}
    const mount = el || (() => { const d = document.createElement('div'); d.id = 'skycloud-live'; document.body.prepend(d); return d; })();
    if (handle) mount.setAttribute('data-skyhandle', handle);
    const outEl = document.getElementById('skycloud-live-out') || mount;
    const phiEl = document.getElementById('skycloud-phi');
    const handleEl = document.getElementById('skycloud-handle');
    if (handleEl && handle) handleEl.textContent = 'sky://' + handle + (meta && meta.canon ? ' → ' + meta.canon : '');
    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);
    };
    /* S18: Proof-Carrying UI — כל תשובת טופס נשמרת כקבלה ב-webkv */
    const persistReceipt = async (out, siteName, inputs) => {
      if (!out || !out.receipt) return;
      try {
        const key = 'ui:receipt:' + (out.receipt.id || Date.now().toString(36));
        await window.SkyCloud.save(key, { receipt: out.receipt, site: siteName, inputs: inputs, output: out.output, verified: out.verified, ts: Date.now() });
        const rEl = document.getElementById('skycloud-receipt');
        if (rEl) rEl.textContent = 'S18 receipt ' + (out.receipt.id || '').slice(0, 16) + '… · verified=' + !!(out.verified && out.verified.match);
      } catch {}
    };
    const run = async (siteName, inputs, flags) => {
      try {
        const out = await window.SkyCloud.resolveLive(siteName, inputs || {}, Object.assign({ verify: true }, flags || {}));
        render(out);
        await persistReceipt(out, siteName, inputs);
        const zt = await window.SkyCloud.verifyLive(out);
        const tEl = document.getElementById('skycloud-trust');
        if (tEl) tEl.textContent = 'S31 zero-trust · trustServer=false · ok=' + !!zt.ok + ' · receipt=' + !!zt.receiptOk + ' · ∇=' + !!zt.verified;
      } 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 || '?') + (health.db && health.db.lagMs != null ? ' · dbLag=' + health.db.lagMs : '');
    });
  } 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() {
  const resolved = resolveBuildCommand({
    auto: BUILD_COMMAND === 'AUTO' || !String(BUILD_COMMAND || '').trim(),
  });
  const cmd = resolved.command;
  if (!cmd) return Promise.resolve();
  if (buildRunning) return buildRunning;
  log(`מריץ בנייה: ${cmd}` + (resolved.source && resolved.source !== 'config' ? ` (${resolved.source})` : ''));
  buildQuietUntil = Date.now() + 600_000; // ננעל עד שהבנייה מסתיימת (+מרווח)
  buildRunning = new Promise((resolve) => {
    exec(cmd, { 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, opts = {}) {
  const hash = hex(sha256(data));
  const timeoutMs = opts.timeoutMs != null ? opts.timeoutMs : 14_000;
  const servers = BLOSSOM_SERVERS.slice();
  return await new Promise((resolve, reject) => {
    let settled = false;
    let pending = servers.length;
    const okServers = [];
    const errors = [];
    if (!pending) {
      reject(new Error('אין שרתי Blossom'));
      return;
    }
    for (const server of servers) {
      (async () => {
        try {
          if (await blossomHas(server, hash)) {
            okServers.push(server);
            if (!settled) { settled = true; resolve({ hash, servers: [server] }); }
            return;
          }
          const res = await fetch(`${server}/upload`, {
            method: 'PUT',
            headers: { Authorization: blossomAuth('upload', hash), 'Content-Type': contentType },
            body: data,
            signal: AbortSignal.timeout(timeoutMs),
          });
          if (!res.ok) throw new Error(`${server} -> HTTP ${res.status}`);
          okServers.push(server);
          if (!settled) { settled = true; resolve({ hash, servers: [server] }); }
        } catch (e) {
          errors.push(e.message || String(e));
        } finally {
          pending--;
          if (pending === 0 && !settled) {
            if (okServers.length) resolve({ hash, servers: okServers });
            else reject(new Error('אף שרת אחסון לא קיבל את הקובץ: ' + errors.slice(0, 4).join(' | ')));
          }
        }
      })();
    }
  });
}

// ═══════════ 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));
}

/** S153: מפתח Ψ — ECDH, או H(ecdh‖latticeShared) כש-HYBRID_LATTICE פעיל (דטרמיניסטי לזוג peers) */
function psiSessionKey(priv, myPub, peerPub) {
  const ecdh = ecdhKey(priv, peerPub);
  if (!(typeof HYBRID_LATTICE !== 'undefined' && HYBRID_LATTICE)) return ecdh;
  const pubs = [String(myPub || ''), String(peerPub || '')].sort();
  const kemSeed = hex(sha256(Buffer.from('hybrid-psi:' + pubs.join(':')))).slice(0, 32);
  const kem = latticeKemKeygen(kemSeed);
  const enc = latticeKemEncaps(kem.pk, kemSeed + ':enc');
  return sha256(Buffer.concat([ecdh, Buffer.from(enc.shared, 'hex')]));
}

// פורמט תיל תואם-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);

/** SC_API_TOKEN gate for sensitive HTTP routes */
function apiAuthCheck(req) {
  const token = process.env.SC_API_TOKEN;
  if (!token) return { ok: true };
  const hdr = req.headers?.authorization || req.headers?.Authorization || '';
  const bearer = hdr.startsWith('Bearer ') ? hdr.slice(7).trim() : String(hdr || '').trim();
  const alt = req.headers?.['x-api-token'] || req.headers?.['X-Api-Token'];
  return (bearer === token || alt === token) ? { ok: true } : { ok: false, reason: 'unauthorized' };
}

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 = [];
    // Lattice Wave S151–S172 runtime state
    this._vdocs = this.state.vectorDocs || [];
    this._metamorphPlan = metamorphPlan(RUN_SALT);
    this._bftPendingVotes = [];
    this._bftProposal = null;
    this._p2pState = {};
    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 (process.env.SC_TEST && process.env.SC_TEST_NO_LIVE === '1') {
      ensureId();
      deriveKeys();
      return Promise.resolve(this);
    }
    if (!this.readyPromise) this.readyPromise = this._start();
    return this.readyPromise;
  }

  /**
   * חיבור קל לרשת — בלי reconcile/אתר/Living (לפרסום מנוע/אחים בלבד).
   * מונע "תקיעה" של דקות ב־--publish-engine.
   */
  async readyLite(opts = {}) {
    ensureId();
    deriveKeys();
    const min = opts.minRelays != null ? opts.minRelays : 2;
    const timeoutMs = opts.timeoutMs != null ? opts.timeoutMs : 12_000;
    log(`אחים: חיבור קל ל־${min}+ ממסרים (timeout ${Math.round(timeoutMs / 1000)}ש׳) — בלי סנכרון אתר…`);
    this.pool.connectAll();
    const n = await this.pool.waitForConnections(min, timeoutMs);
    if (n === 0) throw new Error('אין חיבור לאף ממסר — בדוק אינטרנט (readyLite)');
    log(`אחים: מחובר ל־${n} ממסרים (מצב lite).`);
    return { ok: true, relays: n };
  }

  async _start() {
    this._starting = true;
    try {
      ensureId();
      deriveKeys();
      // S16: חילוץ קפסולה משובצת לפני פרסום אתר
      if (CAPSULE_EMBED?.b64 && !this.state.capsuleUnpacked) {
        try {
          const u = await this.unpackCapsule(CAPSULE_EMBED);
          if (u.ok) log(`S16 Capsule Runtime: אתר מ-${u.dir}`);
        } catch (err) { log('אזהרה: חילוץ קפסולה נכשל:', err.message); }
      }
      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));
      }
      // SkyHandle / Address stack (לא מקצר npub — גשרים מעליו)
      if (ADDRESS_STACK_AUTO) {
        try { await this.ensureAddressStack({ card: false, handle: SITE_SLUG }); }
        catch (err) { log('אזהרה: Address stack:', err.message); }
      } else if (SITE_SLUG && !this.state.skyHandle) {
        try { await this.publishHandle(SITE_SLUG); } catch (err) { log('אזהרה: SkyHandle:', err.message); }
      }
      log('סינכרון ראשוני הושלם.');
      const h = skyHandleOf(this.state.skyHandle || SITE_SLUG);
      const virt = virtualAddressForPack(this.state);
      log(`כתובת האתר החי של "${PROJECT_NAME}" [${SITE_SLUG}] (זמינה תמיד, גם כשהכול כבוי):`);
      if (virt?.short) log('  קצרה:    ' + virt.short + '  (סיומת המנוע — לא DNS רשום)');
      log('  קנונית:  ' + this.address());
      log('  מיוחדת:  ' + h.sky + '  ·  כניסה: ' + h.atUrl());
      if (this.state.callingCard) log('  calling-card: ' + this.state.callingCard);
      if (PUBLIC_WEBSITE) {
        const ap = gatewayAccessPackPlan({ domain: CUSTOM_DOMAIN || null, virtual: virt });
        log('גישה (אם רשת חוסמת שער — נסו מראה / שתפו את כל הרשימה):');
        log('  ראשי:   ' + ap.pack.primary);
        for (const u of ap.pack.mirrors.filter((m) => m !== ap.pack.primary)) log('  מראה:   ' + u);
        log('  דף מראות: ' + String(ap.pack.primary || '').replace(/\/$/, '') + '/access.html');
        this.state.accessPack = ap.pack;
        saveState(this.state);
      }
      if (CUSTOM_DOMAIN) printDomainSetup();
      // SkyDB: Ω API אוטומטי (נתיב-חם מקומי; לא דורש FULL_NODE)
      if (SKYDB_AUTO || FULL_NODE) {
        try { this.enableDbApi(); } catch (err) { log('SkyDB: enableApi נכשל:', err.message); }
      }
      // S32/S36: worker mesh heartbeat + peers
      if (WORKER_MESH) {
        try { await this.beatWorker({ boot: true }); } catch (err) { log('אזהרה: mesh beat:', err.message); }
      }
      // S22: snapshot בריאות ללא-מקור (גם בלי Node חי אח"כ)
      this.ensureZeroOriginContinuity().catch(() => {});
      // Living Global Address: כתובת = Node-equivalent (mesh+∇+snapshots), כמו חיבור דומיין
      if (LIVING_ADDRESS || this.state.livingAddress) {
        try {
          await this.enableLivingAddress({ republish: true, seed: true, card: !!CUSTOM_DOMAIN });
        } catch (err) { log('אזהרה: Living Address:', err.message); }
      }
      this._started = true;
    } finally {
      this._starting = false;
    }
  }

  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, opts = {}) {
    if (typeof BFT_SYNC !== 'undefined' && BFT_SYNC && !opts.skipBft) {
      const digest = hex(sha256(Buffer.from(JSON.stringify(payload)))).slice(0, 32);
      const votes = opts.bftVotes || this._bftPendingVotes || [];
      const gate = bftGateAccept(digest, votes, { enabled: true });
      if (!gate.ok) {
        log('S151 BFT: מניפסט נדחה — אין קוורום', gate.reason, 'agree=', gate.prepare || 0);
        this._bftLast = gate;
        return { ok: false, bft: gate };
      }
      this._bftLast = gate;
    }
    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)))) };
    }
    return { ok: true };
  }

  /** השוואת מקומי מול ענן — מי עדכני יותר מנצח, לשני הכיוונים */
  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);
    this._maybeDrainOfflineQueue();
  }

  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 ing = sandboxIngest(data.subarray(0, Math.min(data.length, 4096)));
      if (ing.suspicious) {
        cryptoShred(data);
        log('S166: הורדה חסומה — קובץ חשוד:', rel, '(' + ing.kind + ')');
        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;
    const seq = (this.state.chain?.seq ?? this.manifest.seq ?? 0) + 1;
    this.manifest.seq = seq;
    this.manifest.prev = this.state.chain?.hash ?? null;
    const json = JSON.stringify(this.manifest);
    const hash = hex(sha256(Buffer.from(json)));
    if (typeof BFT_SYNC !== 'undefined' && BFT_SYNC) {
      this.bftProposeTip(hash.slice(0, 32));
    }
    const content = encrypt(Buffer.from(json), dManifest());
    await this.pool.publish(makeEvent(KIND_DATA, dManifest(), content));
    this.state.chain = { seq, hash };
    saveState(this.state);
    if (typeof COVERT_MESH !== 'undefined' && COVERT_MESH) {
      try {
        const cov = covertHttpWrap(Buffer.from(json), { key: syncId || keys?.ns || 'covert' });
        await this.webSet('covert:mesh', { ...cov.body, digest: this.state.chain?.hash || null, ts: Date.now() }).catch(() => {});
        const stego = stegoEmbedPng(Buffer.from(this.state.chain?.hash || json.slice(0, 64)));
        const up = await blossomUpload(stego.png, 'image/png').catch(() => null);
        if (up?.servers?.[0]) {
          await this.webSet('covert:stego', { url: `${up.servers[0]}/${up.hash}`, bytes: stego.bytes, ts: Date.now() }).catch(() => {});
        }
      } catch (err) {
        log('S156 COVERT_MESH:', err.message);
      }
    }
    await this._publishSite().catch(() => {});
    if (PUBLIC_WEBSITE) await this._publishWebsite().catch((err) => log('אזהרה: עדכון האתר נכשל:', err.message));
  }

  /**
   * פרסום האתר החי (nsite / NIP-5A): קבצי הפרויקט מועלים לשרתי אחסון
   * מבוזרים, ומניפסט חתום ברשת ממפה כל נתיב לקובץ. שערים ציבוריים מגישים
   * את זה ככתובת חיה — בלי ששום דבר שלך ירוץ ברקע.
   */
  async _publishWebsite(opts = {}) {
    this.state.blobs ??= {};
    // ב---deploy מאמתים מחדש את כל הקבצים מול שרתי האחסון (HEAD זול; מעלה רק חסרים)
    if (this.forceVerifyBlobs) { this.state.blobs = {}; this.forceVerifyBlobs = false; }
    try {
      const plan = inferProjectPublishPlan(ROOT);
      this.state.publishPlan = {
        siteResolved: plan.siteResolved, build: plan.build, ready: plan.ready, ts: Date.now(),
      };
      if (!this._publishPlanLogged) {
        this._publishPlanLogged = true;
        log('תוכנית פרסום:', plan.siteResolved ? `site=${plan.siteResolved}/` : 'site=project-root',
          plan.build.command ? `build=${plan.build.command}` : 'build=none',
          plan.ready ? 'ready' : 'check-config');
      }
    } catch {}
    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;

    // S126 Full-App Merkle Tip — זיהוי שלמות מתמטי על כל הקבצים
    const merkle = fullAppMerkleTip(paths.map(([p, h]) => ({ path: p, digest: h })));
    this.state.fullAppMerkle = merkle;
    await this.webSet('address:merkle', merkle).catch(() => {});

    // Full-app: לא מפרסמים tip חלקי — רק אחרי שכל הקבצים עלו
    const expectedWeb = siteFiles.map(([webPath]) => webPath);
    const fullGate = fullAppPublishGate(expectedWeb, paths, {
      allowPartial: !FULL_APP_STRICT && failed === 0,
      merkleRoot: merkle.merkleRoot,
      expectedMerkle: opts?.expectedMerkle || merkle.merkleRoot,
      requireMerkle: !!opts?.requireMerkle,
    });
    fullGate.merkleRoot = merkle.merkleRoot;
    fullGate.merkleDigest = merkle.digest;
    this.state.fullAppGate = fullGate;
    saveState(this.state);
    await this.webSet('address:full-app', fullGate).catch(() => {});
    if (!fullGate.ok) {
      clearTimeout(this.siteRetryTimer);
      this.siteRetryTimer = setTimeout(() => {
        log(`Full-app: חסרים ${fullGate.nMissing} קבצים — מנסה שוב עד שהאתר שלם…`);
        this.forceVerifyBlobs = true;
        this._publishWebsite().catch((err) => log('אזהרה: עדכון האתר נכשל:', err.message));
      }, 30_000);
      this.siteRetryTimer.unref?.();
      log(`Full-app gate: פרסום tip נדחה — ${fullGate.nMissing} קבצים חסרים (לא סטטי-חלקי).`);
      return { ok: false, reason: fullGate.reason, fullGate, uploaded, failed };
    }

    // קבצים שנכשלו — ניסיון חוזר אוטומטי בעוד דקה, עד שהאתר שלם
    if (failed > 0) {
      clearTimeout(this.siteRetryTimer);
      this.siteRetryTimer = setTimeout(() => {
        log(`ניסיון חוזר להעלאת ${failed} קבצים לאתר…`);
        this._publishWebsite().catch((err) => log('אזהרה: עדכון האתר נכשל:', err.message));
      }, 60_000);
      this.siteRetryTimer.unref?.();
      if (FULL_APP_STRICT) {
        log('Full-app strict: יש כשלון העלאה — tip לא מתעדכן עד שלמות.');
        return { ok: false, reason: 'upload-failures', failed, uploaded };
      }
    }

    // אם ה-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 + Living Global Address: מטא חי — living-mesh לא קורס לסטטי ב-republish
    const living = !!(this._livingAddress || this.state.livingAddress || LIVING_ADDRESS);
    const liveSite = this._defaultLiveSite || this.state.liveSite || (this._sitePolicies?.size ? [...this._sitePolicies.keys()][0] : null)
      || (living ? 'pricing' : null);
    const livePolicies = this._sitePolicies?.size
      ? [...this._sitePolicies.keys()]
      : (this.state.livePolicies || (liveSite ? [liveSite] : (living ? ['pricing', 'access', 'ranking'] : [])));
    if (liveSite || living) {
      try {
        if (liveSite) this._defaultLiveSite = liveSite;
        this.state.liveSite = liveSite || this.state.liveSite || 'pricing';
        this.state.livePolicies = livePolicies;
        this.state.policyCdn = !!(this._policyCdn || this.state.policyCdn || living);
        this.state.policyKit = this._policyKit || this.state.policyKit || null;
        if (living) { this._livingAddress = true; this.state.livingAddress = true; }
        const handle = this.state.skyHandle || skyHandleOf(SITE_SLUG).handle;
        const slaNow = (() => {
          try { return slaScoreFromWorkers(this.state.workers || this.webGet('mesh:workers') || {}); }
          catch { return { workersAlive: 0, score: 0 }; }
        })();
        // S99 Live-Address Health Badge
        const healthBadge = liveAddressHealthBadge(slaNow);
        const livingPlan = living ? livingAddressPlan({
          canonical: globalAddress(),
          sky: 'sky://' + handle,
          at: globalAddress().replace(/\/$/, '') + '/@',
          domain: CUSTOM_DOMAIN || null,
          callingCard: this.state.callingCard || null,
          apiPrefix: API_PREFIX,
          workersAlive: healthBadge.workersAlive,
        }) : null;
        const activeRoots = (this.state.trustRoots || this.webGet('trust:root')?.active || []).filter(Boolean);
        let trustRoot = this.state.trustRoot || this.webGet('trust:root')?.digest || null;
        // S105: חותמים skylive תחת active root כשיש roots
        if (!trustRoot && activeRoots.length) trustRoot = activeRoots[0];
        const virtLive = virtualAddressForPack(this.state);
        const skyliveObj = {
          site: this.state.liveSite,
          policies: livePolicies,
          kit: this.state.policyKit,
          cdn: !!this.state.policyCdn,
          handle,
          sky: 'sky://' + handle,
          canon: globalAddress(),
          short: virtLive?.short || undefined,
          virtual: !!(virtLive?.virtual && virtLive?.short),
          virtualHost: virtLive?.host || undefined,
          skySuffix: virtLive?.suffix || normalizeSkySuffix(SKY_SUFFIX) || undefined,
          open: virtLive?.open || globalAddress().replace(/\/$/, '') + '/@',
          npub: virtLive?.npub || npub(),
          domain: CUSTOM_DOMAIN || null,
          mirrors: mirrorAddresses(),
          access: '/access.html',
          accessJson: '/access.json',
          shareHint: 'If one gateway is blocked, open /access.html or try another mirror — same site',
          forms: { pricing: ['n'], access: ['__authed'], ranking: ['score'] },
          live: true,
          living: !!living,
          staticOnly: false,
          trustServer: false,
          apiPrefix: API_PREFIX,
          mesh: !!living,
          visitorsAreWorkers: !!living,
          snapshots: !!living,
          sync: true,
          users: true,
          domainParity: true,
          mode: living ? 'living-mesh' : 'policy-cdn',
          v: living ? 4 : 3,
          workersAlive: healthBadge.workersAlive,
          health: healthBadge,
          trustRoot: trustRoot || undefined,
          note: living
            ? 'nsite=shell; Node-equivalent via Ω mesh + visitor workers + snapshots + ∇; same as CUSTOM_DOMAIN; also lives on space-class paths (connect, not control)'
            : undefined,
          livingPlan: livingPlan || undefined,
          space: (() => {
            const presence = spacePresencePlan({
              canon: globalAddress(),
              tip: this.state.immortalTip?.digest || this.webGet('immortal:tip')?.digest || null,
              flockId: this.state.flock?.flockId || null,
              living: !!living,
              probes: this.state.antennaProbes || [],
              spaceClass: SPACE_MESH.spaceClass,
            });
            this.state.spacePresence = presence;
            return {
              presence,
              pin: spaceAddressPinGate(presence, {}),
              phase: this.state.spaceDance?.phase ?? null,
              hopReceipts: this.state.spaceHopReceipts?.skylive || this.webGet('skylive:hopReceipts') || null,
              roam: this.state.addressRoam?.roamDigest || this.webGet('address:roam')?.roamDigest || null,
              roamDirty: !!this.state.skyliveRoamDirty,
              continuations: this.webGet('space:hop:continuations') || null,
              rain: this.webGet('space:hop:rain') || null,
              connectNotControl: true,
            };
          })(),
        };
        // S117 Skylive Dual-Root Freeze
        const trustState = this.webGet('trust:root') || { phase: null, active: activeRoots };
        const dualLive = skyliveDualRootFreezeGate(trustState, {});
        if (!dualLive.ok) {
          log('אזהרה: S117 skylive dual-root freeze — living לא מתפרסם');
          skyliveObj.living = false;
          skyliveObj.live = false;
          skyliveObj.policies = [];
          skyliveObj.dualRootFreeze = true;
        }
        // S105 Trust-Root Skylive Pin — מדיניות חיה רק תחת active roots
        const rootGate = trustRootSkyliveGate(skyliveObj, activeRoots, {});
        if (!rootGate.ok) {
          log('אזהרה: S105 skylive trust-root pin —', rootGate.reason);
          skyliveObj.policies = [];
          skyliveObj.live = false;
          skyliveObj.living = false;
          skyliveObj.livePinned = false;
          skyliveObj.pinReason = rootGate.reason;
        } else {
          skyliveObj.livePinned = true;
        }
        // Dedicated mesh dial meta (side-effects path)
        if (SIDE_EFFECT_DIAL || living) {
          skyliveObj.dial = dedicatedMeshDialPlan({ handle, channel: 'dial:' + handle });
        }
        const gate = livingAddressAntiStaticGate({ ...skyliveObj, hasCloudWeb: true, hasSkylive: true }, [['/cloud-web.js'], ['/skylive.json']]);
        if (!gate.ok && living) log('אזהרה: living anti-static gate:', gate.reasons.join(', '));
        skyliveObj.gate = { ok: gate.ok, reasons: gate.reasons, rootGate };
        // S142: clear roam dirty after embedding current roam into skylive
        if (this.state.skyliveRoamDirty) {
          this.state.skyliveRoamDirty = false;
          skyliveObj.space = skyliveObj.space || {};
          skyliveObj.space.roamPublished = skyliveObj.space.roam || true;
        }
        const liveMeta = Buffer.from(JSON.stringify(skyliveObj), 'utf8');
        const { hash } = await blossomUpload(liveMeta, 'application/json');
        this.state.blobs[hash] = true;
        paths.push(['/skylive.json', hash]);
        this.state.livingPlan = livingPlan || this.state.livingPlan;
        saveState(this.state);
      } catch (err) { log('אזהרה: פרסום skylive.json נכשל:', err.message); }
    }

    // חבילת גישה נגד חסימת שער בודד — /access.json + /access.html בכל המראות
    if (PUBLIC_WEBSITE) {
      try {
        const ap = gatewayAccessPackPlan({ domain: CUSTOM_DOMAIN || null, mirrors: mirrorAddresses(), virtual: virtLive });
        this.state.accessPack = ap.pack;
        saveState(this.state);
        await this.webSet('access:pack', ap.pack).catch(() => {});
        const aj = Buffer.from(JSON.stringify(ap.pack, null, 2), 'utf8');
        const upJ = await blossomUpload(aj, 'application/json');
        this.state.blobs[upJ.hash] = true;
        paths.push(['/access.json', upJ.hash]);
        const ah = Buffer.from(buildAccessHtml(ap.pack), 'utf8');
        const upH = await blossomUpload(ah, 'text/html');
        this.state.blobs[upH.hash] = true;
        paths.push(['/access.html', upH.hash]);
        if (!this._accessPackLogged) {
          this._accessPackLogged = true;
          log('גישה נגד חסימות: /access.html · ' + ap.nMirrors + ' מראות (שתפו את הרשימה, לא רק קישור אחד)');
        }
      } catch (err) { log('אזהרה: פרסום access pack נכשל:', err.message); }
    }

    // SkyHandle entry: /@/index.html — כניסה קצרה מעל ה-npub
    if (!paths.some(([p]) => p === '/@/index.html' || p === '/@')) {
      try {
        const handle = this.state.skyHandle || skyHandleOf(SITE_SLUG).handle;
        const atHtml = Buffer.from(`<!DOCTYPE html><html lang="he" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>sky://${handle}</title>
<style>body{font-family:Georgia,serif;background:#0b0e14;color:#e6e9ef;padding:48px 20px;max-width:640px;margin:0 auto}a{color:#7aa2f7}</style></head><body>
<h1>sky://${handle}</h1>
<p>SkyHandle → כתובת קנונית. המדיניות רצה אצל המבקר (∇).</p>
<p><a href="/">כניסה לאתר</a></p>
<div id="skycloud-live" data-skysite="${this._defaultLiveSite || 'pricing'}" data-skylive="1" data-skyhandle="${handle}"></div>
<script src="/cloud-web.js"></script></body></html>`, 'utf8');
        const { hash } = await blossomUpload(atHtml, 'text/html');
        this.state.blobs[hash] = true;
        paths.push(['/@/index.html', hash]);
        paths.push(['/@', hash]);
      } catch (err) { log('אזהרה: פרסום /@ נכשל:', err.message); }
    }

    // מנוע הסינכרון לדפדפן — חובה לכתובת חיה (anti-static: בלי SDK = סטטי בלבד)
    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);
        if (this._livingAddress || LIVING_ADDRESS) {
          throw new Error('Living Global Address: חובה /cloud-web.js — פרסום סטטי בלבד נחסם');
        }
      }
    }
    if ((this._livingAddress || this.state.livingAddress || LIVING_ADDRESS) && !paths.some(([p]) => p === '/skylive.json')) {
      log('אזהרה: Living Address בלי skylive.json — מצב עלול להיראות סטטי');
    }

    // אם עדיין אין 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) {
        this.kv[k] = entry;
        if (notify) for (const fn of this.kvListeners) { try { fn(k, entry.v); } catch {} }
        continue;
      }
      if (xiCanon(cur.v) === xiCanon(entry.v)) {
        if (entry.ts > cur.ts) this.kv[k] = { ...cur, ts: entry.ts, hlc: entry.ts };
        continue;
      }
      if ((KV_MERGE || 'smart') === 'lww') {
        if (entry.ts > cur.ts) {
          this.kv[k] = entry;
          if (notify) for (const fn of this.kvListeners) { try { fn(k, entry.v); } catch {} }
        }
        continue;
      }
      // smart: תמיד מאחדים שדות; סקלרים מתנגשים מהצד החדש יותר
      const m = smartMergeRecords(cur, entry, { policy: 'smart' });
      const prev = xiCanon(cur.v);
      this.kv[k] = m.record;
      if (notify && xiCanon(m.record.v) !== prev) {
        for (const fn of this.kvListeners) { try { fn(k, m.record.v); } catch {} }
      }
    }
  }

  async set(key, value, opts = {}) {
    await this.ready();
    this.kv[key] = { v: value, ts: hlcNow() };
    if (opts.offlineOnly) return { ok: true, offline: true, reason: 'offline-only' };
    const poolUp = (this.pool?.sockets?.size || 0) > 0;
    if (!poolUp) {
      this.offlineCommit({ key, value });
      return { ok: true, offline: true, reason: 'pool-down' };
    }
    try {
      const content = encrypt(Buffer.from(JSON.stringify(this.kv)), dKv());
      await this.pool.publish(makeEvent(KIND_DATA, dKv(), content));
      return { ok: true, offline: false };
    } catch (err) {
      this.offlineCommit({ key, value });
      return { ok: true, offline: true, reason: 'publish-failed', error: err.message };
    }
  }

  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 — נתונים חיים משותפים עם האתר הציבורי ───────────────
  // ערוץ ציבורי (לא מוצפן): גם האתר בדפדפן וגם המכשירים שלך קוראים
  // וכותבים בו — זה מה שמאפשר מערכת חיה עם משתמשים ישירות מהכתובת.

  _ingestCovertMeshEntry(entry) {
    if (!(typeof COVERT_MESH !== 'undefined' && COVERT_MESH)) return null;
    const ing = covertMeshIngestPlan(entry.v, { key: syncId || keys?.ns || 'covert' });
    if (ing.ok) {
      try {
        const payload = JSON.parse(ing.payload.toString('utf8'));
        const accepted = this._acceptManifest(payload, { skipBft: true });
        if (accepted?.ok !== false) {
          this.state.covertMeshLast = { digest: ing.digest, ts: entry.ts, bytes: ing.payload.length };
          saveState(this.state);
        }
      } catch (err) {
        log('S156 COVERT ingest:', err.message);
      }
    }
    return ing;
  }

  _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) {
        this.webkv[key] = entry;
        if (key === 'covert:mesh') this._ingestCovertMeshEntry(entry);
        if (key === 'bft:votes' && Array.isArray(entry.v)) {
          for (const v of entry.v) {
            if (v?.ok && v.digest === this._bftProposal?.digest) {
              this._bftPendingVotes = [...(this._bftPendingVotes || []), v];
            }
          }
        }
        if (notify) for (const fn of this.webListeners) { try { fn(key, entry.v, ev.pubkey); } catch {} }
        return;
      }
      if (xiCanon(cur.v) === xiCanon(entry.v)) {
        if (entry.ts > cur.ts) this.webkv[key] = { ...cur, ts: entry.ts };
        return;
      }
      if (key === 'covert:mesh') this._ingestCovertMeshEntry(entry);
      if ((KV_MERGE || 'smart') === 'lww') {
        if (entry.ts > cur.ts) {
          this.webkv[key] = entry;
          if (notify) for (const fn of this.webListeners) { try { fn(key, entry.v, ev.pubkey); } catch {} }
        }
        return;
      }
      const m = smartMergeRecords(cur, entry, { policy: 'smart' });
      const prev = xiCanon(cur.v);
      this.webkv[key] = m.record;
      if (notify && xiCanon(m.record.v) !== prev) {
        for (const fn of this.webListeners) { try { fn(key, m.record.v, ev.pubkey); } catch {} }
      }
    } catch {}
  }

  async webSet(key, value) {
    await this.ready();
    const entry = { v: value, ts: hlcNow() };
    this.webkv[key] = entry;
    if (process.env.SC_TEST && process.env.SC_TEST_NO_LIVE === '1') return;
    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 = {}) {
    const m = String(method).toUpperCase();
    const auth = opts.auth === true || (opts.auth !== false && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(m) && !opts.public);
    this.routes.push({ method: m, path: pathPattern, fn, static: !!opts.static, auth });
    if (!(process.env.SC_TEST && process.env.SC_TEST_NO_LIVE === '1')) {
      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) {
    // S127: אם מערך — בחירת rendezvous לפי lag/pathClass
    if (Array.isArray(url)) {
      const plan = multiBridgeRendezvousPlan(url, {});
      this.state.bridgePlan = plan;
      saveState(this.state);
      if (!plan.rendezvous?.url) throw new Error('S127: אין bridge rendezvous');
      url = plan.rendezvous.url;
      this._bridgeAlternates = plan.bridges.map((b) => b.url).filter((u) => u !== url);
    }
    this.bridgeUrl = String(url).replace(/\/$/, '');
    this.ready().then(() => this.startLive()).catch(() => {});
    log(`Ω: גשר שרת פעיל — בקשות API מהכתובת הגלובלית מגושרות אל ${this.bridgeUrl}`);
    return this;
  }

  /** S127: בחירת/ריענון multi-bridge rendezvous */
  pickBridgeRendezvous(bridges, opts = {}) {
    const plan = multiBridgeRendezvousPlan(bridges || this.state.bridgePlan?.bridges || [], opts);
    this.state.bridgePlan = plan;
    if (plan.rendezvous?.url) {
      this.bridgeUrl = String(plan.rendezvous.url).replace(/\/$/, '');
      this._bridgeAlternates = plan.bridges.map((b) => b.url).filter((u) => u !== this.bridgeUrl);
    }
    saveState(this.state);
    return plan;
  }

  _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;
  }

  _apiAuth(req) { return apiAuthCheck(req); }

  /** ביצוע בקשה מקומית: גשר → שרת אמיתי, אחרת נתיב רשום */
  async _serveRequest(req) {
    const pathname = req.path.split('?')[0];
    // נתיבים מקומיים (SkyDB / route) קודמים לגשר — אחרת bridge בולע /api/db/*
    const hit = this._matchRoute(req.method, pathname);
    if (hit) {
      if (hit.r.auth && process.env.SC_API_TOKEN) {
        const auth = this._apiAuth(req);
        if (!auth.ok) {
          return { status: 401, headers: { 'content-type': 'application/json' }, body: Buffer.from(JSON.stringify({ error: 'unauthorized', reason: auth.reason })).toString('base64'), enc: 'base64' };
        }
      }
      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 = psiSessionKey(keys.priv, keys.pubkey, 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 = psiSessionKey(keys.priv, keys.pubkey, 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;
    const destRoot = path.resolve(destDir) + path.sep;
    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; // תלויות יותקנו מחדש
      if (!zipEntrySafe(rel)) { log('ZIP: דילוג על נתיב לא בטוח:', rel); continue; }
      const full = path.resolve(path.join(destDir, rel));
      if (!full.startsWith(destRoot)) continue; // הגנת path-traversal (Zip Slip)
      const ing = sandboxIngest(e.data);
      if (ing.suspicious && !opts.allowSuspicious) {
        cryptoShred(e.data);
        log('S166: דילוג על קובץ חשוד ב-ZIP:', rel, '(' + ing.kind + ')');
        continue;
      }
      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, siteDir: sd, plan: projectPublishPipelinePlan(ROOT) };
  }

  /** npm install בפרויקט (אחרי ZIP) */
  async npmInstall(opts = {}) {
    if (!fs.existsSync(path.join(opts.cwd || ROOT, 'package.json'))) {
      return { ok: false, reason: 'no-package-json' };
    }
    log('מתקין תלויות: npm install…');
    const r = await runShellCommand('npm install', { cwd: opts.cwd || ROOT, timeoutMs: opts.timeoutMs || 600_000 });
    if (!r.ok) log('אזהרה: npm install:', (r.stderr || r.reason || '').trim().split('\n').slice(-2).join(' '));
    else log('npm install הושלם.');
    return r;
  }

  /** מפעיל npm run dev|start זמנית, מזהה פורט, וסוגר — לא משאיר שרת ברקע */
  async probeDevServer(opts = {}) {
    await this.stopDevServer({ silent: true });
    let pkg;
    try { pkg = JSON.parse(await fsp.readFile(path.join(opts.cwd || ROOT, 'package.json'), 'utf8')); }
    catch { return { ok: false, reason: 'no-package-json' }; }
    const dev = devServerScriptPlan(pkg);
    if (!dev.script) return { ok: false, reason: 'no-dev-script', skipped: true };

    const isWin = process.platform === 'win32';
    const timeoutMs = opts.timeoutMs || 45_000;
    log(`מפעיל זמנית ${dev.command} לזיהוי פורט (ייסגר אוטומטית)…`);

    const probe = await new Promise((resolve) => {
      const child = spawn(isWin ? 'npm.cmd' : 'npm', ['run', dev.script], {
        cwd: opts.cwd || ROOT,
        env: { ...process.env, BROWSER: 'none', CI: 'true' },
        windowsHide: true,
        detached: !isWin,
      });
      this._devServerChild = child;
      let out = '';
      let settled = false;
      const finish = (result) => {
        if (settled) return;
        settled = true;
        clearTimeout(timer);
        resolve(result);
      };
      const timer = setTimeout(() => finish({ ok: false, reason: 'timeout', output: out }), timeoutMs);
      const onData = (d) => {
        out += d.toString();
        const parsed = parseDevServerUrlPlan(out);
        if (parsed.url) finish({ ok: true, url: parsed.url, port: parsed.port, output: out, script: dev.script });
      };
      child.stdout?.on('data', onData);
      child.stderr?.on('data', onData);
      child.on('error', (err) => finish({ ok: false, reason: err.message, output: out }));
      child.on('close', (code) => {
        if (!settled) finish({ ok: false, reason: 'dev-exited', code, output: out });
      });
    });

    if (probe.ok && probe.url) {
      let httpOk = false;
      try {
        const res = await fetch(probe.url, { signal: AbortSignal.timeout(6000) });
        httpOk = res.status < 500;
      } catch { httpOk = false; }
      this.state.devProbe = { url: probe.url, port: probe.port, script: probe.script, httpOk, ts: Date.now() };
      saveState(this.state);
      const cloudJs = path.join(ROOT, 'cloud.js');
      if (patchCloudKnobFile(cloudJs, 'LOCAL_BRIDGE', probe.url)) {
        log(`LOCAL_BRIDGE נשמר: ${probe.url} (לשימוש בגשר API בכתובת הגלובלית)`);
      }
      if (opts.keepAlive) {
        this.bridge(probe.url);
        log('גשר dev פעיל (keepAlive=true).');
        return { ok: true, bridge: probe.url, port: probe.port, keptAlive: true, httpOk };
      }
    }

    await this.stopDevServer();
    if (probe.ok) {
      log(`זוהה שרת dev על ${probe.url} — נסגר (לא רץ ברקע).`);
      return { ok: true, bridge: probe.url, port: probe.port, keptAlive: false, httpOk: !!this.state.devProbe?.httpOk };
    }
    log('לא זוהה פורט dev — ממשיכים לפרסום סטטי בלבד.');
    return { ok: false, reason: probe.reason || 'no-url', skipped: probe.reason === 'dev-exited' };
  }

  /** עוצר שרת dev זמני — לא להשאיר npm run dev ברקע */
  async stopDevServer(opts = {}) {
    if (this._devServerChild?.pid) {
      treeKillProcess(this._devServerChild.pid);
      this._devServerChild = null;
      if (!opts.silent) log('שרת dev נעצר — לא ימשיך ברקע.');
    }
    return { ok: true };
  }

  applyPublishKnobs(plan) {
    const cloudJs = path.join(ROOT, 'cloud.js');
    const applied = [];
    if (plan?.siteResolved) {
      if (patchCloudKnobFile(cloudJs, 'SITE_DIR', plan.siteResolved)) applied.push('SITE_DIR');
    }
    if (plan?.build?.command) {
      if (patchCloudKnobFile(cloudJs, 'BUILD_COMMAND', plan.build.command)) applied.push('BUILD_COMMAND');
    }
    if (!PUBLIC_WEBSITE) {
      if (patchCloudKnobFile(cloudJs, 'PUBLIC_WEBSITE', true)) applied.push('PUBLIC_WEBSITE');
    }
    if (applied.length) log('עודכנו knobs:', applied.join(', '));
    return { ok: true, applied };
  }

  /**
   * ZIP → חילוץ → npm install → build → dev probe (נסגר) → deploy → כתובת גלובלית
   * עדכון פרויקט: אותו צינור בלי ZIP (opts.skipExtract).
   */
  async importZipAndPublish(zipPath, opts = {}) {
    ensureId(); deriveKeys();
    let imported = null;
    if (!opts.skipExtract && zipPath) {
      imported = await this.importZip(zipPath, opts);
    }
    return this.projectPublishPipeline({ ...opts, imported });
  }

  async projectPublishPipeline(opts = {}) {
    ensureId(); deriveKeys();
    const pipe = projectPublishPipelinePlan(ROOT, opts);
    if (opts.npmInstall !== false && fs.existsSync(path.join(ROOT, 'package.json'))) {
      await this.npmInstall(opts);
    }
    this.applyPublishKnobs(pipe.publish);
    await runBuild();
    let probe = null;
    if (opts.probeDev !== false) {
      probe = await this.probeDevServer({ keepAlive: false, timeoutMs: opts.devTimeoutMs });
    }
    await this.stopDevServer();
    this.forceVerifyBlobs = true;
    this.publishedThisRun = false;
    await this.ready();
    await this.publishLivingSnapshots({}).catch(() => {});
    await this.ensureAddressStack({}).catch(() => {});
    const addr = globalAddress();
    const siteDir = resolveSiteDir();
    log('צינור פרסום הושלם:', addr, siteDir ? `site=${siteDir}/` : '', probe?.bridge ? `bridge=${probe.bridge}` : '');
    return {
      ok: true,
      address: addr,
      mirrors: mirrorAddresses(),
      siteDir,
      bridge: probe?.bridge || this.state.devProbe?.url || null,
      imported: opts.imported || null,
      plan: pipe,
    };
  }

  // ─────────── Θ: ריפוי-עצמי — רענון שכפול + אימות שלמות אוטומטי ───────────
  // מחזורי: (1) מרענן את המניפסט והזהות ברשת כדי לשמור שכפול רחב (מונע
  // דעיכת TTL בממסרים), (2) מאמת מחדש שקבצי האתר עדיין קיימים בשרתי האחסון
  // ומעלה מחדש חסרים. תיקון-עצמי בלי מגע יד אדם, מונע-נתונים.
  async _healSweep() {
    const lite = process.env.SC_TEST_LITE === '1';
    try {
      if (!lite) {
        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 + S2/S7/S19: מדיניות + genome + rollback על fork
      if (!lite && (pulse.scenario?.scenario === 'logic_fork' || scAct === 'freeze_audit')) {
        try {
          const cur = await this.resolveName('active-policy', { waitMs: 2000 });
          if (cur.seq != null && cur.seq > 0) {
            const prev = await this.resolveName('active-policy', { seq: cur.seq - 1, waitMs: 2000 });
            if (prev.target) await this.publishName('active-policy', prev.target).catch(() => {});
          }
          await this.pinDbOnFork().catch(() => {});
        } catch {}
      }
      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;
          const app = scenarioAppSelect(pulse.scenario, this._scenarioAppMap || { relay_degraded: 'degraded', high_load: 'degraded', logic_fork: 'degraded', db_lag: 'degraded', default: 'full' });
          if (app) {
            await this.publishName('app:ui', 'policy:' + app).catch(() => {});
            // S7: אותה ערוץ גם ב-webkv — ה-shell קורא SkyCloud.load
            await this.webSet('app:ui', { profile: app, policy: 'policy:' + app, scenario: pulse.scenario?.scenario, ts: Date.now() }).catch(() => {});
          }
        }
      }
      // S19 Autonomic App Genome
      const genome = capsuleGenomeSelect(pulse.scenario, this._capsuleGenomeMap);
      if (this._capsuleGenome !== genome) {
        this._capsuleGenome = genome;
        await this.publishName('capsule:profile', genome).catch(() => {});
        await this.webSet('capsule:genome', { profile: genome, scenario: pulse.scenario?.scenario, ts: Date.now() }).catch(() => {});
        await this.webSet('app:ui', { profile: genome === 'offline-nabla' ? 'degraded' : (genome === 'degraded' ? 'degraded' : 'full'), genome, ts: Date.now() }).catch(() => {});
        if (genome === 'offline-nabla') await this.ensureZeroOriginContinuity({ genome }).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 {
        const sla = this.slaReport();
        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,
          mind: this.mindStats(), autopilot: this._lastAutopilot?.top || null,
          sla: sla.sla, workersAlive: sla.sla.workersAlive, throughput: sla.throughput,
          cdnBest: sla.cdnBest, trustServer: false,
        });
        await this.webSet('phi:sla', sla).catch(() => {});
        // UX נפילה + התראות מפעיל
        const ux = relayOutageUxPlan({ relaysUp: rep.length }, { lang: 'he' });
        await this.webSet('ux:sync', ux).catch(() => {});
        await this.webSet('app:ux', {
          profile: ux.profile, code: ux.code, userMessage: ux.userMessage,
          userAction: ux.userAction, canWriteNetwork: ux.canWriteNetwork, ts: Date.now(),
        }).catch(() => {});
        if (ux.code === 'RELAYS_DOWN' && ALERTS.onRelaysDown) {
          await this.notifyOpsAlert('relays-down', ux).catch(() => {});
        } else if (ux.code === 'RELAYS_WEAK' && ALERTS.onRelaysWeak) {
          await this.notifyOpsAlert('relays-weak', ux).catch(() => {});
        }
      } catch {}
      // S32/S36 mesh beat בכל heal
      if (WORKER_MESH) try { await this.beatWorker({ heal: true }); } catch {}
      // S111 Health-Badge Auto Republish
      if (this._pendingSkyliveRepublish) {
        this._pendingSkyliveRepublish = false;
        try { await this._publishSite(); } catch {}
      }
      // SpaceMesh-Ν: ריקוד מסלולים + DTN custody בכל heal
      if (SPACE_MESH.enabled || this._spaceMesh) {
        try {
          await this.enableSpaceMesh({ force: true });
          await this.dtnCustodySweep().catch(() => {});
        } catch {}
      }
      // Immortal-Ι / ADO: שכפול עצמי + מניעת הכחדה (חי על האנטנות גם בלי מקור)
      if (IMMORTAL_MESH.enabled || this._immortalMesh || ADO.enabled || this._ado) {
        try {
          if (!this._immortalMesh) await this.enableImmortalMesh({ skipPack: true }).catch(() => {});
          else await this.immortalSweep({ skipPack: true }).catch(() => {});
          if ((ADO.enabled || this._ado) && !this._ado) await this.enableADO({ skipPack: true }).catch(() => {});
        } catch {}
      }
      // S129: sticky failover — יציאה רק אחרי N probes ירוקים
      try {
        const sticky = failoverStickyTtlPlan(this.state.cdnFailover || this.webGet('cdn:failover'), Date.now());
        const probes = this.state.cdnProbe?.results || this.state.antennaProbes || [];
        const healSticky = stickyProbeHealPlan(sticky, probes, { streak: this.state.stickyGreenStreak || 0 });
        if (healSticky.release) {
          this.state.cdnFailover = { ...(this.state.cdnFailover || {}), stickyUntil: 0, failover: false };
          this.state.stickyGreenStreak = 0;
          saveState(this.state);
        } else if (sticky.sticky) {
          const okN = probes.filter((p) => p?.ok).length;
          this.state.stickyGreenStreak = (probes.length && okN === probes.length)
            ? (this.state.stickyGreenStreak || 0) + 1
            : 0;
          saveState(this.state);
        }
      } catch {}
      // S130: drain living queue אחרי cutover
      try {
        const trust = this.state.trustRotate || this.webGet('trust:rotate') || {};
        const dq = dualRootLivingQueuePlan(trust, this.state.livingQueue || [], {});
        if (dq.drain) {
          this.state.livingQueue = [];
          saveState(this.state);
          await this.publishDbCheckpoint({ force: true }).catch(() => {});
          if (this._livingAddress || LIVING_ADDRESS) await this.publishLivingSnapshots({}).catch(() => {});
        }
      } catch {}
      // S112 Stale-Snapshot Autorefresh — peer חי מרענן snapshots living
      try {
        if (this._livingAddress || LIVING_ADDRESS || this._forceSnapRefresh) {
          const paths = this.webGet('living:snaps')?.paths || ['/api/health', '/api/sla', '/api/trust', '/api/address'];
          const plan = staleSnapshotAutorefreshPlan({ forceLiveMesh: !!this._forceSnapRefresh, reason: 'snapshot-stale' }, { paths });
          if (plan.refresh || this._livingAddress || LIVING_ADDRESS) {
            for (const p of plan.paths || paths) {
              const r = this.routes.find((x) => x.method === 'GET' && x.path === p);
              if (r) await this._snapshotRoute(r).catch(() => {});
            }
          }
          this._forceSnapRefresh = false;
        }
      } catch {}
      // S39: עובד מנקז jobs של Mind שיועדו אליו
      if (WORKER_MESH) try { await this.drainMindMeshJobs(); } catch {}
      // S42: עובדים עונים לבקשות attest פתוחות
      try {
        for (const [k, v] of Object.entries(this.webGetAll())) {
          if (!k.startsWith('trust:req:')) continue;
          const req = v;
          if (!req?.digest || Date.now() - (req.ts || 0) > 180_000) continue;
          await this.attestTrustQuorum({ digest: req.digest, kind: req.kind, quorum: req.need || 2, request: false }).catch(() => {});
        }
      } catch {}
      // S48 + S44 canary gate — רק canary worker מושך בשלב canary; אחרי promote כולם
      try {
        if (this._autoPullCapsule == null && WORKER_MESH) this._autoPullCapsule = true;
        if (this._autoPullCapsule) {
          const meta = this.webGet('capsule:canary');
          const gate = canaryHealGate(meta, DEVICE);
          const pulled = this.state.capsuleMeshPulled?.digest;
          if (gate.pull === 'canary') {
            const tip = this.webGet('capsule:canary:tip') || this.state.capsuleCanaryTip;
            if (tip?.digest && tip.digest !== pulled) {
              const pr = await this.pullCapsuleFromMesh({ tip });
              if (!pr.ok) await this.rollbackCapsuleCanary({ reason: 'pull-fail', pull: pr }).catch(() => {});
            }
          } else if (gate.pull === 'mesh') {
            const tip = this.webGet('capsule:mesh:tip') || this.state.capsuleMeshTip;
            if (tip?.digest && tip.digest !== pulled) {
              await this.pullCapsuleFromMesh({ tip }).catch(() => {});
            }
          }
          // S52: rollback אוטומטי אם canary חי ו-SLA קרס
          if (meta?.stage === 'canary') {
            const sla = slaScoreFromWorkers(this.state.workers || {});
            const plan = canaryRollbackPlan(meta, sla, null);
            if (plan.shouldRollback) {
              this._promoteSlaStreak = 0;
              await this.rollbackCapsuleCanary({ reason: plan.reasons.join(','), plan }).catch(() => {});
            } else {
              // S79 Autonomic Promote Heal — N מחזורי SLA תקינים → promote
              const slaGate = slaPromoteGate(sla, { minSla: 50 });
              const ap = autonomicPromoteHealPlan(meta, slaGate, this._promoteSlaStreak || 0, { needStreak: this._promoteNeedStreak || 3 });
              this._promoteSlaStreak = ap.streak || 0;
              await this.webSet('heal:promote-streak', { ...ap, ts: Date.now() }).catch(() => {});
              if (ap.promote) {
                // S90: אם earnings-hold — streak מתאפס בתוך promote
                const promo = await this.promoteCapsuleCanary({ digest: meta.digest, rollbackOnFail: true }).catch((e) => ({ ok: false, error: e.message }));
                if (promo && promo.reason === 'promote-earnings-hold') this._promoteSlaStreak = 0;
              }
            }
          }
        }
      } catch {}
      // S55/S61: Node חי → drain continuum + distill-CDN
      try {
        if (this._dbApiEnabled || this._fullNodeServer) {
          const q = this.webGet('mind:continuum:queue') || this.state.mindContinuumQueue;
          if (q?.items?.some((i) => i.status === 'queued')) {
            await this.drainDistillSeedContinuum({ limit: 2, allowSelfQuorum: false }).catch(() => {});
          }
        }
      } catch {}
      // S57/S63: שיקוף seed + חימום CDN
      try {
        if (this._autoSeedGossip) await this.gossipVisitorSeeds({ warmCdn: true }).catch(() => {});
      } catch {}
      // S66: סיבוב trust-root אוטונומי כשקוורום יציב
      try {
        if (this._autoTrustHeal !== false) await this.runAutonomicTrustHeal().catch(() => {});
      } catch {}
      // S34 CDN probe תקופתי (קל)
      if (CDN_LATTICE && (!this._lastCdnProbe || Date.now() - this._lastCdnProbe > 600_000)) {
        this._lastCdnProbe = Date.now();
        this.probeCdnLattice({ limit: 3 }).catch(() => {});
      }
      // S152: drain תור offline כשהממסרים חיים
      try { this._maybeDrainOfflineQueue(); } catch {}
      // S163: DTN forward כשיש peers ברשת
      try {
        const q = this.state.dtnQueue || [];
        if (q.length && (this.pool?.sockets?.size > 0 || (this.state.workers || []).length > 0)) {
          const fwd = transportForwardPlan(q, true);
          if (fwd.forwarded?.length) {
            this.state.dtnQueue = fwd.remaining;
            saveState(this.state);
          }
        }
      } catch {}
      // S158: סוכן opt-in — טיק טלמטריה מקומי
      if (typeof NODE_AGENT !== 'undefined' && NODE_AGENT) {
        try {
          const tick = await this.agentTick({
            buildErrors: this._buildErrors || 0,
            chunkBytes: this._lastUploadBytes || 0,
            needCompute: !!(this._xiStanding?.length),
          });
          if (tick.actions?.length) this._lastAgentTick = tick;
        } catch {}
      }
      // S173–S180: Swarm Mesh — registry + handoffs + sessions על SYNC_ID
      if (SWARM_MESH.enabled) try { await this.swarmMeshTick(); } catch {}
      // S27 Telemetry Autopilot — פעולות עצמאיות לפי דופק+Mind+DB
      try { await this.runTelemetryAutopilot(pulse); } catch {}
      // S41: genome swap חם אם pulse שינה פרופיל
      try {
        const want = capsuleGenomeSelect(pulse.scenario, this._capsuleGenomeMap);
        if (want && want !== this._capsuleGenome) await this.hotGenomeCapsuleSwap(want);
      } 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 Map()).set(jTag, Date.now());
            if (this._xiSeen.size > 10_000) {
              let oldestK = null, oldestT = Infinity;
              for (const [k, ts] of this._xiSeen) { if (ts < oldestT) { oldestT = ts; oldestK = k; } }
              if (oldestK != null) this._xiSeen.delete(oldestK);
            }
            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;
    let paid = null;
    if ((opts.signReceipt || opts.payReceipt) && ev.receipt) {
      signedReceipt = await this.signReceipt(ev.receipt);
      // S1+S18: תשלום מאומת + שמירת קבלת-UI
      if (opts.payReceipt) {
        try { paid = await this.creditFromReceipt(signedReceipt, ev.output, { site: slug, allowReplay: !!opts.allowReplay }); } catch (err) { paid = { ok: false, error: err.message }; }
      }
      await this.publishUiReceipt(signedReceipt, { site: slug, output: ev.output }).catch(() => {});
    }
    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, paid,
    };
  }

  /** 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;
  }

  // ═══════════ SkyHandle · Pack · Secret doors S1–S22 ═══════════

  /** מפרסם שם קצר מעל ה-npub (S17 Handle Lattice) */
  async publishHandle(handle) {
    ensureId(); deriveKeys();
    // לא await ready() מתוך _start — אותו Promise עדיין לא resolved (deadlock)
    if (!this._starting && !this._started) await this.ready();
    const h = skyHandleOf(handle || SITE_SLUG);
    const canon = globalAddress();
    await this.publishName('handle', h.handle);
    await this.publishName('alias:' + h.handle, 'site:' + canon);
    this.state.skyHandle = h.handle;
    saveState(this.state);
    await this.webSet('sky:handle', { ...h, canon, pubkey: keys.pubkey, ts: Date.now() }).catch(() => {});
    log(`SkyHandle: ${h.sky} → ${canon} (כניסה גם ב-${h.atUrl()})`);
    return { ...h, canon, pubkey: keys.pubkey };
  }

  async resolveHandle(handle, opts = {}) {
    const slug = slugify(handle) || '';
    const alias = await this.resolveName('alias:' + slug, opts);
    if (alias.target && String(alias.target).startsWith('site:')) {
      return { handle: slug, canon: String(alias.target).slice(5), seq: alias.seq, sky: 'sky://' + slug, historical: !!opts.seq };
    }
    const tip = await this.resolveName('handle', opts);
    if (tip.target && (!slug || tip.target === slug)) {
      return { handle: tip.target, canon: globalAddress(), seq: tip.seq, sky: 'sky://' + tip.target };
    }
    return { handle: slug, canon: undefined, sky: 'sky://' + slug };
  }

  /** S181: מפרסם כתובת וירטואלית קצרה slug.suffix מעל npub (Roam virt: + alias:) */
  async publishVirtualAddress(opts = {}) {
    ensureId(); deriveKeys();
    if (!this._starting && !this._started) await this.ready();
    const slug = opts.handle || this.state.skyHandle || SITE_SLUG;
    const plan = virtualGlobalAddressPlan({
      slug,
      suffix: opts.suffix != null ? opts.suffix : SKY_SUFFIX,
      enabled: opts.enabled != null ? opts.enabled : SKY_SUFFIX_ENABLED,
      canonical: globalAddress(),
      npub: npub(),
      pubkey: keys.pubkey,
    });
    if (!plan.ok) return plan;
    await this.publishName(plan.roamVirt, 'site:' + plan.canon);
    await this.publishName(plan.roamAlias, 'site:' + plan.canon);
    this.state.virtualAddress = plan;
    saveState(this.state);
    await this.webSet('address:virtual', { ...plan, ts: Date.now() }).catch(() => {});
    log(`כתובת קצרה (וירטואלית): ${plan.short} → ${plan.canon}`);
    return plan;
  }

  async resolveVirtualAddress(hostOrUrl, opts = {}) {
    const parsed = parseVirtualHost(hostOrUrl);
    if (!parsed) return { ok: false, reason: 'parse', host: hostOrUrl };
    const virt = await this.resolveName('virt:' + parsed.host, opts);
    if (virt.target && String(virt.target).startsWith('site:')) {
      return {
        ok: true,
        ...parsed,
        canon: String(virt.target).slice(5),
        seq: virt.seq,
        historical: !!opts.seq,
        virtual: true,
      };
    }
    const alias = await this.resolveName('alias:' + parsed.slug, opts);
    if (alias.target && String(alias.target).startsWith('site:')) {
      return {
        ok: true,
        ...parsed,
        canon: String(alias.target).slice(5),
        seq: alias.seq,
        historical: !!opts.seq,
        virtual: true,
        via: 'alias',
      };
    }
    return { ok: false, reason: 'not-found', ...parsed };
  }

  /** Calling-card ב-Blossom — URL קצר יחסית ל-npub subdomain */
  async publishCallingCard(opts = {}) {
    if (!keys) { ensureId(); deriveKeys(); }
    if (!this.readyPromise) await this.ready();
    const h = skyHandleOf(opts.handle || this.state.skyHandle || SITE_SLUG);
    const canon = globalAddress();
    const html = Buffer.from(`<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=${canon}"><title>${h.sky}</title>
<script>location.replace(${JSON.stringify(canon)})</script></head>
<body><p><a href="${canon}">${h.sky}</a> → canonical nsite</p></body></html>`, 'utf8');
    const { hash, servers } = await blossomUpload(html, 'text/html');
    const url = `${servers[0]}/${hash}`;
    await this.publishName('calling-card', url);
    this.state.callingCard = url;
    saveState(this.state);
    log(`Calling-card: ${url} (${h.sky})`);
    return { url, hash, ...h, canon };
  }

  /** S1: כלכלת-קבלות — קרדיט מאומת + ledger נגד שימוש כפול */
  async creditFromReceipt(signed, output, opts = {}) {
    const body = signed?.event ? signed : signed;
    const v = signed?.event ? this.verifyReceipt(signed, output) : { ok: verifyReceiptBody(body, output), reason: 'body' };
    if (!v.ok) throw new Error('S1: קבלה לא תקפה — ' + (v.reason || ''));
    const id = body.id || signed.id;
    this.state.spentReceipts ??= {};
    if (id && this.state.spentReceipts[id] && !opts.allowReplay) {
      throw new Error('S1: קבלה כבר נוצלה (double-spend)');
    }
    const who = body.visitor || signed.visitor || signed.pubkey || 'anon';
    const cred = await this.creditWarmer(who, opts.site || 'receipt');
    if (opts.pointAs) await this.publishName(opts.pointAs, 'rep:' + cred.score);
    if (opts.marketBest) await this.publishName('best:' + (opts.site || 'receipt'), 'rep:' + cred.score).catch(() => {});
    if (id) {
      this.state.spentReceipts[id] = { who, score: cred.score, ts: Date.now() };
      const keysSpent = Object.keys(this.state.spentReceipts);
      if (keysSpent.length > 5000) delete this.state.spentReceipts[keysSpent[0]];
      saveState(this.state);
    }
    await this.webSet('receipt:last', { id, who, score: cred.score, ts: Date.now() }).catch(() => {});
    return { ...cred, receiptId: id, verified: true };
  }
  async receiptPay(signed, output, opts = {}) { return this.creditFromReceipt(signed, output, opts); }

  /** S3: הצעת מחיר כבולת-הקשר + TTL/nonce לשוק (+ S110 lag tax) */
  quoteSealed(price, ctx = {}, opts = {}) {
    let lag = opts.lagEwmaMs;
    if (lag == null) {
      try { lag = this._mindDayRoll().latencyEwmaMs; } catch { lag = this.state?.mindDay?.latencyEwmaMs || 0; }
    }
    const taxed = opts.skipLagTax ? null : lagTaxQuotePlan(price, lag, opts);
    const sealedPrice = taxed ? taxed.price : price;
    const live = liveContext(ctx);
    const bound = contextBind(sealedPrice, live);
    let ct = null;
    try { ct = contextSealPayload(Buffer.from(JSON.stringify(sealedPrice)), live).ct; } catch {}
    const quote = {
      v: 1, price: sealedPrice, basePrice: price, lagTax: taxed || null, ...bound, ct,
      nonce: opts.nonce || hex(crypto.randomBytes(8)),
      exp: opts.ttlMs ? Date.now() + opts.ttlMs : (opts.exp || null),
      site: opts.site || 'pricing',
      id: null,
    };
    quote.id = hex(sha256(Buffer.from('scquote:v1:' + xiCanon({ price: sealedPrice, ctxDigest: quote.ctxDigest, nonce: quote.nonce })))).slice(0, 32);
    return quote;
  }
  openQuote(quote, ctx = {}) {
    if (quote?.exp && Date.now() > quote.exp) throw new Error('S3: quote expired');
    if (quote?.ct) {
      const buf = contextOpenPayload(quote.ct, liveContext(ctx));
      return JSON.parse(buf.toString('utf8'));
    }
    if (!contextCheck(quote, liveContext(ctx))) throw new Error('S3: quote context mismatch');
    return quote.output !== undefined ? quote.output : quote.price;
  }
  async publishQuote(price, ctx = {}, opts = {}) {
    const q = this.quoteSealed(price, ctx, opts);
    await this.webSet('quote:' + q.id, q).catch(() => {});
    // S122 Lag-Tax Quote Market
    const mkt = lagTaxQuoteMarketPlan(q, opts);
    if (mkt.market) {
      await this.webSet('quote:market:' + q.id, { ...mkt, quote: q, ts: Date.now() }).catch(() => {});
      if (this._skydb && opts.ledger !== false) {
        try {
          await this.db.put('mind:paid', 'lagtax:' + q.id, {
            digest: q.id, usd: mkt.usd, tier: mkt.tier, worker: DEVICE,
            receiptKind: 'lag-tax-quote', ts: Date.now(),
          }, { force: true });
        } catch {}
      }
    }
    return { ...q, market: mkt };
  }

  /** S4: שברי-DAG — מקומי או הרכבה מ-digests זרים (מבקרים) */
  async runVisitorShards(dag, inputs = {}, parts = 2, opts = {}) {
    let out;
    if (Array.isArray(opts.foreignDigests) && opts.foreignDigests.length) {
      out = {
        digests: opts.foreignDigests,
        assemble: xiAssembleDigests(opts.foreignDigests),
        parts: opts.foreignDigests.length,
        output: opts.output,
        fullDigest: opts.fullDigest || null,
      };
    } else {
      out = await this.evalFragments(dag, inputs, parts);
    }
    const full = opts.skipFull ? null : await xiClientEval(dag, inputs);
    const trace = full ? branchTrace(full) : (opts.trace || null);
    let cons = null;
    if (opts.reportBranch !== false && trace) cons = await this.reportBranch(opts.site || 'shard', trace).catch(() => null);
    if (cons?.fork || opts.forceFreeze) {
      this.scenarios.observe({ logicFork: true, relaysUp: 6, phiAvg: 0, vitality: 1, scores: [1] });
      await this.freezePinOffline({ reason: 'shard-fork' }).catch(() => {});
    }
    return { ...out, trace, consensus: cons, output: out.output ?? full?.output };
  }

  /** S5: אימוץ מדיניות זרה — קוורום digests מרובי-מחברים */
  async adoptForeignPolicy(pubOrPath, name, opts = {}) {
    const pol = await this.getPolicy(typeof pubOrPath === 'string' && pubOrPath.includes('/') ? pubOrPath : name, {
      pubkey: typeof pubOrPath === 'string' && !pubOrPath.includes('/') ? pubOrPath : opts.pubkey,
      waitMs: opts.waitMs,
    });
    if (!pol?.dag) throw new Error('S5: מדיניות זרה חסרה');
    const k = opts.quorum || 2;
    let reports = Array.isArray(opts.peerDigests) ? opts.peerDigests.slice() : [];
    // תמיד מוסיפים הערכה מקומית כעד אחד
    const local = await xiClientEval(pol.dag, opts.inputs || {});
    reports.push({ digest: local.dagDigest, author: keys?.pubkey || 'local', output: local.output });
    // אם אין עדים זרים — מאפשרים self-quorum רק עם opts.allowSelfQuorum
    if (reports.length < k && opts.allowSelfQuorum) {
      for (let i = reports.length; i < k; i++) reports.push({ digest: local.dagDigest, author: 'self-' + i });
    }
    const q = quorumDigests(reports, k);
    if (!q.ok) throw new Error('S5: קוורום ∇ נכשל — agree=' + q.agree + '/' + k + (q.rivals ? ' (rival digests)' : ''));
    const slug = slugify(opts.as || name || 'foreign') || 'foreign';
    await this._publishPublicPolicy(slug, pol);
    (this._sitePolicies ??= new Map()).set(slug, pol);
    await this.publishName('adopted:' + slug, 'pol:' + (pol.dagId || q.digest));
    await this.webSet('adopted:' + slug, { dagDigest: q.digest, authors: q.authors, ts: Date.now() }).catch(() => {});
    return { ok: true, name: slug, dagDigest: q.digest, agree: q.agree, authors: q.authors };
  }

  /** S6: סנכרון כוונה — webkv מקומי או מפתח זר */
  async syncIntent(name, opts = {}) {
    const slug = slugify(name) || 'intent';
    let remote = opts.payload || this.webGet('intent:' + slug);
    if (!remote?.diff && opts.foreignKey) remote = this.webGet(opts.foreignKey);
    if (!remote?.diff) return { ok: false, reason: 'no-intent' };
    const base = this._sitePolicies?.get(slug)?.dag || opts.base || { nodes: {}, output: null };
    const applied = dagApplyDiff(base, remote.diff, remote.newerNodes || {});
    if (opts.publish !== false) await this.publishSite(slug, applied);
    if (opts.swapUi) {
      await this.publishName('app:ui', 'policy:' + slug).catch(() => {});
      await this.webSet('app:ui', { profile: slug, ts: Date.now() }).catch(() => {});
    }
    const ev = opts.eval !== false ? await xiClientEval(applied, opts.inputs || {}) : null;
    return { ok: true, name: slug, dag: applied, output: ev?.output, dagDigest: ev?.dagDigest };
  }

  /** S12: החלת schema intent + אכיפה חמה על SkyDB */
  async applySchemaIntent(diff, newerNodes = {}) {
    const schema = this.state.dbSchema || {};
    const asDag = {
      nodes: Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, { value: v }])),
      output: Object.keys(schema)[0] || null,
    };
    const nextDag = dagApplyDiff(asDag, diff, { ...newerNodes, __output: newerNodes.__output });
    const next = {};
    for (const [k, n] of Object.entries(nextDag.nodes || {})) next[k] = n.value;
    this.state.dbSchema = next;
    saveState(this.state);
    if (this._skydb) this._skydb._schema = next;
    await this.publishIntent('schema', nextDag).catch(() => {});
    await this.webSet('schema:tip', { schema: next, ts: Date.now() }).catch(() => {});
    return { schema: next, diff };
  }

  /** S18: שמירת קבלת-UI ל-webkv (+ אימות אופציונלי) */
  async publishUiReceipt(receipt, opts = {}) {
    const body = receipt?.receipt || receipt;
    if (opts.verify && body?.id) {
      if (!verifyReceiptBody(body, opts.output)) throw new Error('S18: קבלת-UI לא תקפה');
    }
    const key = 'ui:receipt:' + (body.id || Date.now().toString(36));
    await this.webSet(key, { receipt: body, site: opts.site || null, output: opts.output, ts: Date.now() });
    return key;
  }

  /** S20: tip checkpoint זר + החלה מטא (לא SQL מלא — digests + pin) */
  async fetchForeignDbTip(pubkey, waitMs = 4000) {
    if (!this._starting && !this._started) await this.ready();
    else { ensureId(); deriveKeys(); }
    return new Promise((resolve) => {
      let found = null;
      const author = String(pubkey);
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [author], '#t': ['skydb-chk'], limit: 3 }],
        (ev) => {
          try {
            if (verifyEvent(ev) !== true) return;
            const body = JSON.parse(ev.content);
            if (!found || (body.ts || 0) > (found.ts || 0)) found = { ...body, author, eventId: ev.id };
          } catch {}
        },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve(found); }, waitMs);
    });
  }

  async applyForeignCheckpoint(tip, opts = {}) {
    if (!tip?.digest) throw new Error('S20: tip חסר');
    // S132: דחיית tip עם lag חורג
    const lagGate = replicaLagBoundGate(this.webGet('skydb:tip'), tip, { boundMs: opts.boundMs });
    if (!lagGate.ok && opts.enforceLag !== false && !opts.force) {
      return { ok: false, reason: lagGate.reason, lag: lagGate };
    }
    this.state.foreignTips ??= {};
    this.state.foreignTips[tip.author || tip.digest.slice(0, 12)] = tip;
    saveState(this.state);
    await this.webSet('skydb:foreign:' + (tip.author || 'x').slice(0, 16), tip).catch(() => {});
    if (opts.pin) {
      this._skydb?.pinCheckpoint({ seq: -1, digest: tip.digest, foreign: true, shards: tip.shards }, { freeze: !!opts.freeze });
      await this.publishName('db:foreign', 'chk:' + tip.digest).catch(() => {});
    }
    // S135: WAL blob — הורדה + שיחזור אמיתי ל-SkyDB כש-replayWal
    let replay = null;
    if ((tip.walBlob || tip.pending) && opts.replayWal) {
      try {
        let pending = tip.pending || null;
        if (!pending && tip.walBlob) {
          const url = (BLOSSOM_SERVERS[0] || '') + '/' + tip.walBlob;
          const res = await fetch(url);
          if (res.ok) pending = await res.json();
        }
        if (Array.isArray(pending) && this._skydb) {
          replay = this._skydb.replayWalPending(pending, { ns: opts.ns || 'wal' });
          this.state.foreignWal = { digest: tip.digest, n: pending.length, applied: replay.applied, ts: Date.now() };
          saveState(this.state);
          await this.webSet('skydb:wal:replay', this.state.foreignWal).catch(() => {});
        } else {
          this.state.foreignWal = { digest: tip.digest, n: Array.isArray(pending) ? pending.length : 0, ts: Date.now() };
          saveState(this.state);
        }
      } catch (err) {
        replay = { ok: false, reason: err.message };
      }
    }
    return { ok: true, digest: tip.digest, pinned: !!opts.pin, replay, lag: lagGate };
  }

  /** S21: מיגרציה דטרמיניסטית — פרסום + החלה */
  async publishMigration(parts = {}) {
    const bundle = migrationBundle({
      handle: parts.handle || this.state.skyHandle || null,
      schemaDiff: parts.schemaDiff || null,
      policyTip: parts.policyTip || this._activePolicyRoam || null,
      ts: parts.ts,
      capsuleHash: parts.capsuleHash || (CAPSULE_EMBED?.b64 ? hex(sha256(Buffer.from(CAPSULE_EMBED.b64.slice(0, 200)))).slice(0, 16) : null),
    });
    // הרחבה: schema/policy/handle מלאים ב-webkv (ה-id נשאר על הליבה)
    const full = { ...bundle, schema: parts.schema || this.state.dbSchema || null, newerNodes: parts.newerNodes || null, policyName: parts.policyName || null };
    await this.publishName('migration:tip', 'mig:' + bundle.id);
    await this.webSet('migration:tip', full).catch(() => {});
    return full;
  }

  async applyMigration(bundle, opts = {}) {
    const b = bundle || this.webGet('migration:tip');
    if (!b?.id) throw new Error('S21: אין bundle');
    const core = { v: b.v || 1, handle: b.handle || null, schemaDiff: b.schemaDiff || null, policyTip: b.policyTip || null, ts: b.ts };
    const id = hex(sha256(Buffer.from('skymig:v1:' + xiCanon(core)))).slice(0, 32);
    if (id !== b.id && !opts.skipVerify) throw new Error('S21: migration id mismatch');
    const out = { id: b.id, applied: [] };
    if (b.schemaDiff && b.newerNodes) {
      await this.applySchemaIntent(b.schemaDiff, b.newerNodes);
      out.applied.push('schema');
    } else if (b.schema) {
      this.state.dbSchema = b.schema;
      if (this._skydb) this._skydb._schema = b.schema;
      saveState(this.state);
      out.applied.push('schema-replace');
    }
    if (b.handle) {
      await this.publishHandle(b.handle);
      out.applied.push('handle');
    }
    if (b.policyTip) {
      await this.publishName('active-policy', b.policyTip).catch(() => {});
      await this.webSet('app:ui', { profile: String(b.policyTip).replace(/^policy:/, ''), ts: Date.now() }).catch(() => {});
      out.applied.push('policy');
    }
    await this.webSet('migration:applied', { id: b.id, at: Date.now(), applied: out.applied }).catch(() => {});
    return out;
  }

  /** S22: רצף אפס-מקור — tip אמיתי + מיזוג כש-Node חוזר */
  async ensureZeroOriginContinuity(opts = {}) {
    const pin = this._skydb?._pinned || null;
    const health = {
      node: !!(this._dbApiEnabled || this._fullNodeServer || this.bridgeUrl),
      omega: !!(this.bridgeUrl || this._fullNodeServer),
      db: this._skydb?.health?.() || null,
      genome: this._capsuleGenome || opts.genome || 'offline-nabla',
      handle: this.state.skyHandle || SITE_SLUG,
      dbPin: pin,
      dbTip: this.webGet('skydb:tip') || null,
      schema: this.state.dbSchema || null,
      ts: Date.now(),
    };
    await this.snapshot('/api/health', health).catch(() => {});
    await this.webSet('continuity:tip', health).catch(() => {});
    this.state.continuityTip = health;
    saveState(this.state);
    return health;
  }

  /** S22: מיזוג כש-Node חוזר — משחרר freeze אם tip תואם/חדש */
  async mergeContinuityOnReturn() {
    const tip = this.state.continuityTip || this.webGet('continuity:tip');
    if (!this._dbApiEnabled) this.enableDbApi();
    const live = await this.publishDbCheckpoint().catch(() => null);
    if (this._skydb?._freezeWrites) {
      // משחררים אם אין fork פעיל או אם מבקשים
      this._skydb.unfreezeWrites();
    }
    const merged = { ok: true, hadTip: !!tip, liveDigest: live?.digest || null, genome: this._capsuleGenome || 'full', ts: Date.now() };
    await this.webSet('continuity:merged', merged).catch(() => {});
    await this.ensureZeroOriginContinuity({ genome: this._capsuleGenome || 'full' });
    return merged;
  }

  // ─── צירופים חדשים (המצאות על הדלתות) ───

  /** PayThenSeal: S1→S10+S11+S15 — קבלה → כתיבה כבולה */
  async payThenSeal(ns, key, value, signed, ctx = {}, opts = {}) {
    const pay = await this.receiptPay(signed, opts.receiptOutput !== undefined ? opts.receiptOutput : value, opts);
    const put = await this.db.sealedPut(ns, key, value, ctx, {
      requireReceipt: true, receipt: signed?.event ? signed : signed, receiptOutput: opts.receiptOutput !== undefined ? opts.receiptOutput : value,
      policy: opts.policy, force: opts.force,
    });
    if (opts.checkpoint !== false) await this.publishDbCheckpoint().catch(() => {});
    return { pay, put };
  }

  /** CtxQuoteSettle: S3→S1→S11 — פתיחת הצעה + תשלום + זכאות */
  async settleQuote(quoteIdOrQuote, ctx = {}, opts = {}) {
    const quote = typeof quoteIdOrQuote === 'string' ? this.webGet('quote:' + quoteIdOrQuote) : quoteIdOrQuote;
    if (!quote) throw new Error('settleQuote: quote חסרה');
    const price = this.openQuote(quote, ctx);
    let signed = opts.receipt;
    if (!signed) {
      const body = makeReceiptBody('quote:' + (quote.id || 'x'), { quoteId: quote.id }, price, { visitor: opts.visitor || keys?.pubkey });
      signed = await this.signReceipt(body);
    }
    const pay = await this.receiptPay(signed, price, { site: quote.site || 'pricing', marketBest: true });
    const ent = await this.db.put('entitlements', quote.id || pay.receiptId, { price, paid: true, who: pay.who, ts: Date.now() }, {
      requireReceipt: true, receipt: signed, receiptOutput: price, force: opts.force,
    });
    return { price, pay, entitlement: ent };
  }

  /** FreezePinOffline: S2/S4/S13/S19/S22 */
  async freezePinOffline(opts = {}) {
    const pin = await this.pinDbOnFork();
    // צילום שורות רגישות (ns שצוין)
    if (opts.ns && this._skydb) {
      const rows = {};
      for (const r of this._skydb.query(opts.ns, opts.prefix || '')) rows[opts.ns + '\0' + r.key] = r.value;
      this._skydb.snapshotRowsForPin(rows);
    }
    this._capsuleGenome = 'offline-nabla';
    await this.publishName('capsule:profile', 'offline-nabla').catch(() => {});
    await this.webSet('capsule:genome', { profile: 'offline-nabla', reason: opts.reason || 'fork', ts: Date.now() }).catch(() => {});
    await this.webSet('app:ui', { profile: 'degraded', ts: Date.now() }).catch(() => {});
    const cont = await this.ensureZeroOriginContinuity({ genome: 'offline-nabla' });
    return { pin, continuity: cont };
  }

  /** SchemaBoundFragments: S12+S14 — שאילתה טהורה עם שורות DB כקלט */
  async queryPureBound(dag, opts = {}) {
    if (!dagIsPure(dag)) throw new Error('S14: DAG לא טהור');
    const inputs = { ...(opts.inputs || {}) };
    if (opts.ns) {
      const rows = this.db.query(opts.ns, opts.prefix || '');
      inputs.__rows = rows.map((r) => ({ key: r.key, value: r.value }));
      inputs.__schema = this.state.dbSchema || null;
    }
    const parts = Math.max(1, Math.min(8, opts.parts | 0 || 2));
    const out = await this.evalFragments(dag, inputs, parts);
    if (opts.persistReceipt) {
      const body = makeReceiptBody('query-pure', inputs, out.assemble, { visitor: opts.visitor });
      await this.publishUiReceipt(body, { site: 'query-pure', output: out.assemble });
    }
    return { ...out, inputsBound: !!opts.ns };
  }

  /** HandleSchemaHop: S17+S12+S21 — מיגרציית קפסולה מלאה */
  async capsuleMeshMigrate(parts = {}) {
    const bundle = await this.publishMigration(parts);
    if (parts.apply !== false) await this.applyMigration(bundle, { skipVerify: false });
    if (parts.foreignPub) {
      const tip = await this.fetchForeignDbTip(parts.foreignPub, parts.waitMs || 4000);
      if (tip) await this.applyForeignCheckpoint(tip, { pin: !!parts.pinForeign });
    }
    return bundle;
  }

  /** S16: חילוץ קפסולה משובצת */
  async unpackCapsule(embed = CAPSULE_EMBED) {
    if (!embed?.b64) return { ok: false, reason: 'no-capsule' };
    const dir = path.join(ROOT, embed.outDir || '.sky-unpacked');
    const raw = zlib.inflateSync(Buffer.from(embed.b64, 'base64'));
    const archive = JSON.parse(raw.toString('utf8'));
    fs.mkdirSync(dir, { recursive: true });
    let n = 0;
    for (const [rel, b64] of Object.entries(archive.files || {})) {
      const dest = path.join(dir, rel);
      fs.mkdirSync(path.dirname(dest), { recursive: true });
      fs.writeFileSync(dest, Buffer.from(b64, 'base64'));
      n++;
    }
    this.state.capsuleUnpacked = dir;
    saveState(this.state);
    log(`S16 Capsule: חולצו ${n} קבצים אל ${dir}`);
    return { ok: true, dir, files: n };
  }

  /**
   * S16 Pack — אורז תיקייה לתוך עותק בודד של cloud.js
   * node cloud.js --pack <dir> [--out file] [--keep-id]
   */
  async packCapsule(srcDir, opts = {}) {
    const abs = path.isAbsolute(srcDir) ? srcDir : path.join(ROOT, srcDir);
    if (!fs.existsSync(abs)) throw new Error('pack: תיקייה חסרה ' + abs);
    const files = {};
    const walk = (dir, base) => {
      for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
        if (e.name === 'node_modules' || e.name.startsWith('.') || e.name === 'cloud.js') continue;
        const full = path.join(dir, e.name);
        const rel = path.relative(base, full).replace(/\\/g, '/');
        if (e.isDirectory()) walk(full, base);
        else if (e.isFile() && fs.statSync(full).size < 2_000_000) {
          files[rel] = fs.readFileSync(full).toString('base64');
        }
      }
    };
    walk(abs, abs);
    const b64 = zlib.deflateSync(Buffer.from(JSON.stringify({ v: 1, files })), { level: 9 }).toString('base64');
    const embed = { v: 1, b64, outDir: '.sky-unpacked', site: SITE_SLUG, packedAt: Date.now(), nFiles: Object.keys(files).length };
    let src = fs.readFileSync(__filename, 'utf8');
    if (!opts.keepId) src = src.replace(/const SYNC_ID = '[^']*';/, "const SYNC_ID = 'AUTO';");
    if (src.includes('const CAPSULE_EMBED = null;')) {
      src = src.replace('const CAPSULE_EMBED = null;', 'const CAPSULE_EMBED = ' + JSON.stringify(embed) + ';');
    } else {
      src = src.replace(/const CAPSULE_EMBED = [^;]+;/, 'const CAPSULE_EMBED = ' + JSON.stringify(embed) + ';');
    }
    // SITE_DIR לקפסולה
    src = src.replace(/const SITE_DIR = '[^']*';/, "const SITE_DIR = '.sky-unpacked';");
    const outPath = opts.out || path.join(ROOT, (opts.name || SITE_SLUG || 'app') + '.sky.js');
    fs.writeFileSync(outPath, src);
    log(`S16 Pack: ${embed.nFiles} קבצים → ${outPath} (${(fs.statSync(outPath).size / 1024).toFixed(0)} KB). הרצה: node ${path.basename(outPath)}`);
    return { out: outPath, ...embed };
  }

  // ═══════════ SkyLattice-ΔB: SkyDB על Ω + checkpoints לרשת ═══════════

  /** S8: מפרסם tip digests לרשת + webkv (+ EC defer / S135 WAL ship) */
  async publishDbCheckpoint(opts = {}) {
    if (!this._skydb) return null;
    await this.ready();
    // S130: dual-root — תור במקום ship מיידי
    const trust = this.state.trustRotate || this.webGet('trust:rotate') || {};
    const qPlan = dualRootLivingQueuePlan(trust, this.state.livingQueue || [], {});
    if (qPlan.hold && !opts.force) {
      this.state.livingQueue = [...(this.state.livingQueue || []), { kind: 'skydb-chk', ts: Date.now() }];
      saveState(this.state);
      return { ok: false, reason: 'dual-root-queued', queued: true };
    }
    const pathHint = (this.state.antennaProbes || [])[0] || { ok: true, rttMs: 40 };
    const sync = eventualSyncPlan(pathHint, {
      heavy: true, alwaysOn: this.state.spaceDance?.alwaysOn, immediate: opts.immediate, force: opts.force,
    });
    const window = contactWindowShipGate(this.state.spaceDance, { heavy: true, force: opts.force });
    if ((sync.action === 'defer-dtn' || sync.action === 'hold-custody' || !window.ship) && !opts.force) {
      const { body, pending } = this._skydb.checkpoint();
      body.pendingMeta = pending.length;
      await this.dtnSend({ kind: 'skydb-wal', digest: body.digest, n: pending.length, merkle: body.digest }, { forceDeliver: false }).catch(() => {});
      this.state.walHold = { body, pending, ts: Date.now(), reason: sync.reason || window.reason };
      saveState(this.state);
      return { ok: false, deferred: true, reason: sync.reason || window.reason, body };
    }
    const { body, pending } = this._skydb.checkpoint();
    // version vector
    const vv = versionVectorMerge(this.state.vv || {}, { [DEVICE]: (this.state.vv?.[DEVICE] | 0) + 1 });
    this.state.vv = vv.vector;
    body.vv = vv.vector;
    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 + שידור ל-peers (S135)
    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(() => {});
        }
        const peers = meshPeersFromRegistry(this.state.workers || {});
        const ship = sideEffectWalShipPlan(pending, peers, { dance: this.state.spaceDance, force: opts.force });
        body.walShip = ship;
        if (ship.ship) {
          await this.webSet('skydb:wal:ship', { digest: ship.digest, n: ship.n, peers: ship.peers, ts: Date.now() }).catch(() => {});
          await this.dtnSend({ kind: 'wal-ship', digest: ship.digest, walBlob: body.walBlob, n: ship.n }, { forceDeliver: !!this.state.spaceDance?.alwaysOn }).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(opts = {}) {
    if (!this._skydb) return null;
    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, { freeze: opts.freeze !== false });
    await this.publishName('db:pin', 'seq:' + (pin.seq | 0) + ':d:' + (pin.digest || '')).catch(() => {});
    await this.webSet('db:pin', { ...pin, ts: Date.now() }).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),
      ux: this.syncUxStatus({ lang: 'he' }),
      ts: Date.now(),
    }));
    this.route('GET', '/api/ux/sync', async (req) => {
      const q = req.query || {};
      return json(this.syncUxStatus({ lang: q.lang || 'he' }));
    });
    this.route('GET', '/api/access', async () => {
      const ap = gatewayAccessPackPlan({ domain: CUSTOM_DOMAIN || null, mirrors: mirrorAddresses(), virtual: virtualAddressForPack(this.state) });
      return json({ ...ap.pack, fromState: this.state.accessPack || null, preferredGateway: this.state.preferredGateway || preferredGatewayHost() });
    });
    this.route('GET', '/api/recovery/kit', async () => json(this.exportRecoveryKit({ writeFile: false })));
    this.route('POST', '/api/recovery/kit', async () => json(this.exportRecoveryKit({ writeFile: true })));
    this.route('POST', '/api/alerts/test', async (req) => {
      let b = {};
      try { b = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try { return json(await this.notifyOpsAlert(b.kind || 'test', b.detail || { message: 'SkyCloud alert test', code: 'TEST' }, { force: true })); }
      catch (err) { return json({ error: err.message }, 400); }
    });

    this.route('GET', '/api/client/plan', async () => json(this.nativeClientPlan()));
    this.route('GET', '/api/client/update', async () => {
      const man = this.state.clientUpdate?.manifest || this.webGet('client:update') || null;
      return json(man || { ok: false, reason: 'no-client-update-yet', hint: 'node cloud.js --client-update' });
    });
    this.route('POST', '/api/client/update', async (req) => {
      let b = {};
      try { b = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try { return json(await this.publishClientUpdate({ ...b, upload: b.upload !== false })); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/client/login-sync', async (req) => {
      let b = {};
      try { b = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try { return json(this.clientLoginSync(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/client/embed', async (req) => {
      let b = {};
      try { b = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try { return json(this.embedClientScaffold(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/client/import-local', async (req) => {
      let b = {};
      try { b = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try { return json(await this.importLocalData(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });

    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);
      // S120 Canary Read Replica Fanout
      const cMeta = this.webGet('capsule:canary') || this.state.capsuleCanary;
      const peers = db.peers || [DEVICE];
      const fan = canaryReadReplicaFanoutPlan(cMeta, peers, DEVICE);
      if (fan.fanout === 'replicas' && fan.canaries.includes(DEVICE) && req.headers?.['x-skydb-replica'] !== '1') {
        return json({
          error: 'canary-read-replicas-only', reason: fan.reason, fanout: fan.peers, ns, key,
        }, 403);
      }
      if (req.headers?.['x-skydb-sealed'] === '1') {
        try { return json({ ns, key, value: db.sealedGet(ns, key, req.query || {}), sealed: true, fanout: fan }); }
        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;
      // S32: אם המפתח שייך ל-peer אחר — מחזירים redirect (לא ממציאים SQL בשער)
      try {
        const own = db.shardOf(key);
        if (db._enforceOwner && own.owner && own.owner !== DEVICE) {
          return json({ redirect: true, owner: own.owner, shard: own.shard, ns, key }, 307);
        }
        const out = body.sealed
          ? await db.sealedPut(ns, key, value, body.ctx || {}, { receipt: body.receipt, policy: body.policy, policyInputs: body.policyInputs, force: !!body.force })
          : await db.put(ns, key, value, { receipt: body.receipt, policy: body.policy, policyInputs: body.policyInputs, requireReceipt: !!body.requireReceipt, force: !!body.force });
        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' ? await 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()));

    // S14: שאילתה טהורה בשברי-DAG (רק XI_OPS — לא SQL חם) + קשירת שורות
    this.route('POST', '/api/db/_meta/query-pure', async (req) => {
      let body = {};
      try { body = typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || '{}'); } catch {}
      try {
        const out = await this.queryPureBound(body.dag, {
          inputs: body.inputs, parts: body.parts, ns: body.ns, prefix: body.prefix, persistReceipt: !!body.persistReceipt,
        });
        return json({ ok: true, pure: true, ...out });
      } catch (err) { return json({ error: err.message }, 400); }
    });

    this.enableDoorsApi(opts);
    log('ΔB/SkyDB: Ω API פעיל — /api/health · /api/db/* · /api/doors/* (נתיב-חם מקומי; checkpoints לרשת).');
    return this;
  }

  /** Ω: משטחי דלתות-סתרים למוצר */
  enableDoorsApi(opts = {}) {
    if (this._doorsApiEnabled) return this;
    this._doorsApiEnabled = true;
    const json = (body, status = 200) => ({ status, headers: { 'content-type': 'application/json' }, body });
    const read = async (req) => {
      try { return typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || req.body || '{}'); }
      catch { return {}; }
    };

    this.route('POST', '/api/doors/receiptPay', async (req) => {
      const b = await read(req);
      try { return json(await this.receiptPay(b.receipt || b.signed, b.output, b)); }
      catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('POST', '/api/doors/quote', async (req) => {
      const b = await read(req);
      try { return json(await this.publishQuote(b.price, b.ctx || {}, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/doors/quote/settle', async (req) => {
      const b = await read(req);
      try { return json(await this.settleQuote(b.quote || b.quoteId, b.ctx || {}, b)); }
      catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('POST', '/api/doors/shards', async (req) => {
      const b = await read(req);
      try { return json(await this.runVisitorShards(b.dag, b.inputs || {}, b.parts || 2, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/doors/adopt', async (req) => {
      const b = await read(req);
      try { return json(await this.adoptForeignPolicy(b.pub || b.path, b.name, b)); }
      catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('POST', '/api/doors/syncIntent', async (req) => {
      const b = await read(req);
      try { return json(await this.syncIntent(b.name, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/doors/migrate', async (req) => {
      const b = await read(req);
      try {
        if (b.apply && (b.bundle || b.id)) return json(await this.applyMigration(b.bundle || b, b));
        return json(await this.capsuleMeshMigrate(b));
      } catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/doors/payThenSeal', async (req) => {
      const b = await read(req);
      try { return json(await this.payThenSeal(b.ns, b.key, b.value, b.receipt || b.signed, b.ctx || {}, b)); }
      catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('POST', '/api/doors/freeze', async (req) => {
      const b = await read(req);
      try { return json(await this.freezePinOffline(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/doors/continuity', async (req) => {
      const b = await read(req);
      try {
        if (b.merge) return json(await this.mergeContinuityOnReturn());
        return json(await this.ensureZeroOriginContinuity(b));
      } catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('GET', '/api/doors/status', async () => json({
      handle: this.state.skyHandle || SITE_SLUG,
      genome: this._capsuleGenome || null,
      freeze: !!this._skydb?._freezeWrites,
      pin: this._skydb?._pinned || null,
      schemaKeys: Object.keys(this.state.dbSchema || {}),
      doors: ['S1','S2','S3','S4','S5','S6','S7','S8-S15','S16-S22','S23-S30','S151-S172','S173-S180'],
      compositions: ['payThenSeal','settleQuote','freezePinOffline','queryPureBound','capsuleMeshMigrate','askMind','latencyServe','echoDistill'],
      mind: this.mindStats(),
      autopilot: this._lastAutopilot || null,
      ts: Date.now(),
    }));

    this.enableMindApi(opts);
    this.enableLatticeWaveApi(opts);
    this.enableSwarmApi(opts);
    return this;
  }

  /** Swarm Mesh S173–S180 — HTTP surface for multi-agent apps on SYNC_ID */
  enableSwarmApi(opts = {}) {
    if (this._swarmApiEnabled) return this;
    this._swarmApiEnabled = true;
    const json = (body, status = 200) => ({ status, headers: { 'content-type': 'application/json' }, body });
    const read = async (req) => {
      try { return typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || req.body || '{}'); }
      catch { return {}; }
    };
    const sse = (events) => ({
      status: 200,
      headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' },
      body: (events || []).map((e) => 'data: ' + JSON.stringify(e) + '\n\n').join('') + '\n',
    });

    this.route('GET', '/api/swarm/status', async () => json({
      enabled: SWARM_MESH.enabled,
      maxAgents: SWARM_MESH.maxAgents,
      agents: this.swarmRegistry().length,
      doors: ['S173', 'S174', 'S175', 'S176', 'S177', 'S178', 'S179', 'S180'],
      ts: Date.now(),
    }));
    this.route('POST', '/api/swarm/register', async (req) => json(await this.swarmRegister(await read(req))));
    this.route('GET', '/api/swarm/nodes', async (req) => {
      const q = req.query || {};
      return json(this.swarmDiscover({
        capability: q.capability || q.cap,
        role: q.role,
        query: q.q || q.query,
      }, { limit: Number(q.limit) || 32 }));
    });
    this.route('POST', '/api/swarm/ask', async (req) => {
      const b = await read(req);
      return json(await this.swarmAsk(b.input ?? b.query ?? b, b));
    });
    this.route('POST', '/api/swarm/handoff', async (req) => {
      const b = await read(req);
      return json(await this.swarmHandoff(b.sessionId, b.toAgentId || b.to, b));
    });
    this.route('POST', '/api/swarm/synthesize', async (req) => {
      const b = await read(req);
      return json(this.swarmSynthesize(b.answers || b.responses || [], b));
    });
    this.route('GET', /^\/api\/swarm\/session\/(?<sid>[^/]+)$/, async (req) => {
      const sid = req.params?.sid || '';
      const session = this.state.swarmSessions?.[sid] || this.webGet('swarm:session:' + sid);
      if (!session) return json({ ok: false, reason: 'not-found', sessionId: sid }, 404);
      return json({ ok: true, session, digest: swarmSessionDigestPlan(sid, session.steps || []) });
    });
    this.route('GET', '/api/swarm/stream', async (req) => {
      const q = req.query || {};
      const sid = q.sessionId || q.sid || '';
      const session = sid ? (this.state.swarmSessions?.[sid] || this.webGet('swarm:session:' + sid)) : null;
      const events = [
        { type: 'swarm:tip', tip: this.webGet('swarm:mesh:tip') || { ts: Date.now() } },
      ];
      if (session) events.push({ type: 'swarm:session', sessionId: sid, steps: session.steps || [] });
      return sse(events);
    });
    this.route('GET', '/api/swarm/tools/search', async (req) => {
      const q = req.query || {};
      return json(this.toolLibrarySearch(q.q || q.query || '', { topK: Number(q.topK) || undefined }));
    });
    this.route('POST', '/api/swarm/tools/search', async (req) => {
      const b = await read(req);
      return json(this.toolLibrarySearch(b.q || b.query || '', b));
    });
    this.route('POST', '/api/swarm/tools/publish', async (req) => {
      const b = await read(req);
      const tool = b.tool || dtaToolPlan(b.task || b.description || 'tool', b);
      return json(await this.toolLibraryPublish(tool, b));
    });
    this.route('POST', '/api/swarm/tools/invoke', async (req) => {
      const b = await read(req);
      const tool = b.tool || this.state.swarmTools?.[b.id] || this.state.adisTools?.[b.id];
      if (!tool) return json({ ok: false, reason: 'tool-not-found' }, 404);
      return json(dtaInvokePlan(tool, b.input ?? b.score ?? 0));
    });
    this.route('POST', '/api/swarm/tools/lazy-inject', async (req) => {
      const b = await read(req);
      const tools = b.tools || (b.query ? this.toolLibrarySearch(b.query).hits.map((h) => h.tool) : []);
      return json(this.toolLibraryLazyInject(tools, b));
    });
    this.route('POST', '/api/swarm/tools/deprecate', async (req) => json(this.toolLibraryDeprecate(await read(req))));

    return this;
  }

  /** Ω: Lattice Wave S151–S172 — HTTP surface */
  enableLatticeWaveApi(opts = {}) {
    if (this._latticeWaveApiEnabled) return this;
    this._latticeWaveApiEnabled = true;
    const json = (body, status = 200) => ({ status, headers: { 'content-type': 'application/json' }, body });
    const read = async (req) => {
      try { return typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || req.body || '{}'); }
      catch { return {}; }
    };

    this.route('GET', '/api/lattice-wave/status', async () => json({
      bft: this.bftStatus(),
      offline: this.offlineQueueStatus(),
      doors: this.latticeDoors(),
      agent: !!(typeof NODE_AGENT !== 'undefined' && NODE_AGENT),
      agentApi: NODE_AGENT_API || null,
      hybridLattice: !!(typeof HYBRID_LATTICE !== 'undefined' && HYBRID_LATTICE),
      covertMesh: !!(typeof COVERT_MESH !== 'undefined' && COVERT_MESH),
      bftSync: !!(typeof BFT_SYNC !== 'undefined' && BFT_SYNC),
      metamorph: this._metamorphPlan?.fingerprint || null,
      covertLast: this.state.covertMeshLast || null,
      dtnDepth: (this.state.dtnQueue || []).length,
      ts: Date.now(),
    }));
    this.route('POST', '/api/lattice-wave/zk/prove', async (req) => {
      const b = await read(req);
      return json(this.zkProve(b.secret, { context: b.context, nonce: b.nonce }));
    });
    this.route('POST', '/api/lattice-wave/zk/verify', async (req) => {
      const b = await read(req);
      return json(this.zkVerify(b.transcript, { checkSecret: b.secret, expectedX: b.expectedX }));
    });
    this.route('POST', '/api/lattice-wave/vsearch', async (req) => {
      const b = await read(req);
      return json(this.vsearch(b.q || b.query || '', b.docs));
    });
    this.route('POST', '/api/lattice-wave/vdoc', async (req) => {
      const b = await read(req);
      return json(this.vdocPut(b.id, b.text));
    });
    this.route('POST', '/api/lattice-wave/crdt', async (req) => {
      const b = await read(req);
      return json(this.crdtApply(b.op || b));
    });
    this.route('POST', '/api/lattice-wave/offline/commit', async (req) => {
      const b = await read(req);
      return json(this.offlineCommit(b.op || b));
    });
    this.route('POST', '/api/lattice-wave/offline/drain', async () => json(await this.drainOfflineQueue()));
    this.route('POST', '/api/lattice-wave/bft/vote', async (req) => {
      const b = await read(req);
      return json(this.bftVoteOnTip(b.voter || b.voterPub, b.phase));
    });
    this.route('POST', '/api/lattice-wave/ax', async (req) => {
      const b = await read(req);
      const tree = b.tree || this.axTree(b.html || '');
      return json(b.action ? this.axAct(tree, b.action) : tree);
    });
    this.route('POST', '/api/lattice-wave/session-vault', async (req) => {
      const b = await read(req);
      if (b.load) return json(this.sessionVaultLoad());
      return json(this.sessionVaultSave(b.cookies || b));
    });
    this.route('POST', '/api/lattice-wave/bft/propose', async (req) => {
      const b = await read(req);
      return json(this.bftProposeTip(b.digest || b.tip));
    });
    this.route('POST', '/api/lattice-wave/bft/commit', async (req) => {
      const b = await read(req);
      return json(this.bftCommitTip(b));
    });
    this.route('GET', '/api/lattice-wave/bft/status', async () => json(this.bftStatus()));
    for (const doorId of ['S168', 'S169', 'S170', 'S171', 'S172']) {
      this.route('POST', `/api/lattice-wave/door/${doorId}`, async (req) => {
        const b = await read(req);
        return json(await this.latticeDoor(doorId, b));
      });
    }

    return this;
  }

  // ═══════════ SkyLattice-Μ: SkyMind Bridge + S23–S30 ═══════════

  configureMind(opts = {}) {
    this._mindCfg = { ...MIND, ...(this._mindCfg || {}), ...opts };
    if (opts.enabled != null) this._mindCfg.enabled = !!opts.enabled;
    // ADIS: לובות API בלתי־מוגבלות (cap רך) — ממוזגות למוח אחד
    if (Array.isArray(opts.apis)) {
      this._mindCfg.apis = opts.apis.slice(0, ADIS.maxApiLobes || 32);
    } else if (opts.apiLobe) {
      this._mindCfg.apis = [...(this._mindCfg.apis || []), opts.apiLobe].slice(0, ADIS.maxApiLobes || 32);
    }
    return this;
  }

  mindStats() {
    const s = this.state.mindStats || { day: '', usd: 0, calls: 0, hits: 0, tokensIn: 0, tokensOut: 0, latencyEwmaMs: 0 };
    const hitRate = s.calls ? s.hits / s.calls : 0;
    return { ...s, hitRate: +hitRate.toFixed(3), budgetUsdPerDay: (this._mindCfg || MIND).budgetUsdPerDay, enabled: !!(this._mindCfg || MIND).enabled };
  }

  _mindDayRoll() {
    const day = new Date().toISOString().slice(0, 10);
    this.state.mindStats ??= { day, usd: 0, calls: 0, hits: 0, tokensIn: 0, tokensOut: 0, latencyEwmaMs: 0 };
    if (this.state.mindStats.day !== day) {
      this.state.mindStats = { day, usd: 0, calls: 0, hits: 0, tokensIn: 0, tokensOut: 0, latencyEwmaMs: this.state.mindStats.latencyEwmaMs || 0 };
    }
    return this.state.mindStats;
  }

  _mindApiKey(cfg) {
    const explicit = cfg.apiKey || process.env[cfg.apiKeyEnv || 'SKYCLOUD_LLM_KEY'] || process.env.OPENAI_API_KEY || '';
    if (explicit) return explicit;
    // LM Studio / Ollama / Jan / local OpenAI-compat — often no real key; Bearer still sent
    const base = String(cfg.baseUrl || MIND.baseUrl || process.env.SKYCLOUD_LLM_URL || '').toLowerCase();
    if (/127\.0\.0\.1|localhost|\[::1\]|0\.0\.0\.0/.test(base)) {
      return cfg.localKey || process.env.SKYCLOUD_LLM_LOCAL_KEY || 'lm-studio';
    }
    return '';
  }

  /** קריאה לספק חיצוני (OpenAI-compat / anthropic / mock) */
  async _mindProviderCall(messages, cfg, opts = {}) {
    if (cfg.provider === 'mock' || opts.mock) {
      const last = messages.filter((m) => m.role === 'user').pop()?.content || '';
      return { content: '⟨mock⟩ ' + String(last).slice(0, 200), usage: { prompt_tokens: 10, completion_tokens: 8 }, model: 'mock' };
    }
    const key = this._mindApiKey(cfg);
    if (!key) throw new Error('SkyMind: חסר מפתח — הגדר SKYCLOUD_LLM_KEY או OPENAI_API_KEY (למקומי: LM Studio/Ollama על localhost לא דורש מפתח אמיתי)');
    const model = opts.model || cfg.model || MIND.model;
    const t0 = Date.now();

    if (cfg.provider === 'anthropic') {
      const url = (cfg.baseUrl || 'https://api.anthropic.com') + '/v1/messages';
      const sys = messages.find((m) => m.role === 'system')?.content;
      const body = {
        model, max_tokens: opts.maxTokens || cfg.maxTokens || 1024,
        system: sys || undefined,
        messages: messages.filter((m) => m.role !== 'system').map((m) => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content })),
      };
      const res = await fetch(url, {
        method: 'POST',
        headers: { 'content-type': 'application/json', 'x-api-key': key, 'anthropic-version': '2023-06-01' },
        body: JSON.stringify(body),
      });
      if (!res.ok) throw new Error('SkyMind anthropic HTTP ' + res.status + ' ' + (await res.text().catch(() => '')).slice(0, 200));
      const j = await res.json();
      const content = (j.content || []).map((c) => c.text || '').join('');
      return { content, usage: { prompt_tokens: j.usage?.input_tokens || 0, completion_tokens: j.usage?.output_tokens || 0 }, model, ms: Date.now() - t0, raw: j };
    }

    // openai-compat (OpenAI, Groq, Together, Ollama, Azure-compatible, …)
    const base = (cfg.baseUrl || 'https://api.openai.com/v1').replace(/\/$/, '');
    const url = base.endsWith('/chat/completions') ? base : base + '/chat/completions';
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'content-type': 'application/json', authorization: 'Bearer ' + key },
      body: JSON.stringify({
        model, messages, max_tokens: opts.maxTokens || cfg.maxTokens || 1024,
        temperature: opts.temperature ?? 0.4,
      }),
    });
    if (!res.ok) throw new Error('SkyMind HTTP ' + res.status + ' ' + (await res.text().catch(() => '')).slice(0, 200));
    const j = await res.json();
    const content = j.choices?.[0]?.message?.content ?? j.choices?.[0]?.text ?? '';
    return {
      content, usage: j.usage || {}, model: j.model || model, ms: Date.now() - t0, raw: j,
    };
  }

  /**
   * S26 SkyMind — צינור מלא:
   * Client → ∇ (מדיניות/דחיסה) → Delta → Cache(SkyDB) → External LLM → Shadow-∇ → Receipt
   */
  async askMind(input, opts = {}) {
    // ADIS: מוח מאוחד (kernel+SLM+API lobes) — רק אחרי enableADIS
    if (this._adis && !opts.skipAdisFusion && opts.adis !== false && !opts.rawProvider) {
      return this.adisAsk(input, { ...opts, useApi: !!(opts.useApi || this._mindCfg?.enabled || MIND.enabled) });
    }
    const cfg = { ...MIND, ...(this._mindCfg || {}), ...opts.cfg };
    if (!cfg.enabled && !opts.force && cfg.provider !== 'mock' && !opts.mock) {
      // מאפשרים אם יש מפתח בפועל
      if (!this._mindApiKey(cfg) && !opts.mock) throw new Error('SkyMind כבוי — configureMind({enabled:true}) או --mind-api');
      cfg.enabled = true;
    }
    const t0 = Date.now();
    const stats = this._mindDayRoll();
    const genome = this._capsuleGenome || 'full';
    const gov = mindGovernor(stats, cfg, genome);
    let system = opts.system || cfg.system;
    // S28: system דינמי מדופק המנוע
    if (this._mindPulseBind) {
      try {
        const p = this._pulse();
        system = (system || '') + `\n[SkyCloud pulse scenario=${p.scenario?.scenario || '?'} vitality=${(p.vitality || 0).toFixed(2)} genome=${this._capsuleGenome || 'full'}]`;
      } catch {}
    }
    let messages = mindNormalizeMessages(input, system);

    // S23/∇: מדיניות מקומית לפני שליחה (allow/deny + אופציונל rewrite prompt)
    if (opts.policy || opts.prePolicy) {
      const polName = opts.policy || opts.prePolicy;
      try {
        const pre = await this.resolveSite(polName, {
          inputs: { ...(opts.policyInputs || {}), __prompt: messages.map((m) => m.content).join('\n').slice(0, 2000), __mind: 1 },
          waitMs: opts.waitMs || 2000,
        });
        if (pre.output === 0 || pre.output === false || pre.output === 'deny') {
          return { ok: false, denied: true, reason: 'pre-policy', source: 'nabla', ms: Date.now() - t0 };
        }
        if (typeof pre.output === 'string' && pre.output.startsWith('prompt:')) {
          messages = mindNormalizeMessages(pre.output.slice(7), system);
        }
      } catch { /* מדיניות חסרה — ממשיכים */ }
    }

    // דחיסה מקומית
    const compact = mindCompactPrompt(messages, opts.compact || {});
    messages = compact.messages;

    // Delta מול tip שיחה ב-SkyDB
    const thread = opts.thread || 'default';
    let prev = null;
    try { prev = this.db.get('mind:thread', thread); } catch {}
    const delta = opts.delta === false ? { messages, delta: false, saved: 0 } : mindDeltaMessages(prev?.messages, messages, opts);
    const sendMsgs = delta.messages;

    const model = opts.model || gov.model || cfg.model;
    const ctx = liveContext(opts.context || {});
    const digest = mindPromptDigest(messages, model, { hour: ctx.hour, lang: ctx.lang, thread });
    if (!opts.skipBudget) stats.calls++;

    // מטמון חם (S26 + S22)
    if (gov.mode !== 'deny') {
      try {
        const cached = this.db.get('mind:cache', digest);
        if (cached && cached.content && (!cached.exp || cached.exp > Date.now())) {
          stats.hits++;
          saveState(this.state);
          return {
            ok: true, content: cached.content, source: 'cache', digest, model: cached.model || model,
            ms: Date.now() - t0, costUsd: 0, delta: delta.delta, savedBytes: delta.saved, governor: gov, compacted: compact.compacted,
          };
        }
      } catch {}
    }

    if (gov.mode === 'cache-only') {
      saveState(this.state);
      return { ok: false, reason: gov.reason, source: 'governor', governor: gov, digest, ms: Date.now() - t0 };
    }

    // קריאה חיצונית
    const prov = await this._mindProviderCall(sendMsgs, { ...cfg, model }, { ...opts, model });
    const tokensIn = prov.usage?.prompt_tokens || Math.ceil(JSON.stringify(sendMsgs).length / 4);
    const tokensOut = prov.usage?.completion_tokens || Math.ceil(String(prov.content).length / 4);
    const cost = mindEstimateCost(tokensIn, tokensOut, cfg);
    if (!opts.skipBudget) stats.usd = +(stats.usd + cost).toFixed(6);
    stats.tokensIn += tokensIn;
    stats.tokensOut += tokensOut;
    stats.latencyEwmaMs = stats.latencyEwmaMs ? Math.round(stats.latencyEwmaMs * 0.7 + (prov.ms || (Date.now() - t0)) * 0.3) : (prov.ms || (Date.now() - t0));

    let content = prov.content;

    // S29 Shadow-∇ על הפלט
    if (opts.shadowPolicy) {
      try {
        const sh = await this.resolveSite(opts.shadowPolicy, {
          inputs: { ...(opts.policyInputs || {}), __output: content, __mind: 1 },
          waitMs: 2000,
        });
        const gated = mindShadowGate(content, sh.output);
        if (!gated.ok) {
          saveState(this.state);
          return { ok: false, denied: true, reason: gated.reason, source: 'shadow-nabla', ms: Date.now() - t0 };
        }
        content = gated.output;
      } catch {}
    }

    // שמירת מטמון + tip שיחה
    const exp = Date.now() + (opts.cacheTtlMs || cfg.cacheTtlMs || MIND.cacheTtlMs);
    const row = { content, model: prov.model, digest, exp, ts: Date.now(), tokensIn, tokensOut, cost };
    try {
      await this.db.put('mind:cache', digest, row, { force: true });
      const nextThread = { messages: [...messages, { role: 'assistant', content }], digest, ts: Date.now() };
      await this.db.put('mind:thread', thread, nextThread, { force: true });
    } catch {}

    // S1/S18: קבלת הוכחה על תשובה
    let receipt = null;
    if (opts.signReceipt !== false) {
      try {
        const body = makeReceiptBody('mind:' + digest, { digest, thread }, content.slice(0, 200), { visitor: opts.visitor || keys?.pubkey });
        receipt = await this.signReceipt(body);
        await this.publishUiReceipt(receipt, { site: 'mind', output: content.slice(0, 200) }).catch(() => {});
      } catch {}
    }

    // S30 Echo Distill — אופציונלי: הופך תשובה יציבה למדיניות חמה
    if (opts.distillAs) {
      try { await this.echoDistill(opts.distillAs, content, { digest }); } catch {}
    }

    saveState(this.state);
    await this.webSet('mind:last', { digest, model: prov.model, cost, ms: Date.now() - t0, source: 'provider', ts: Date.now() }).catch(() => {});

    const result = {
      ok: true, content, source: 'provider', digest, model: prov.model, ms: Date.now() - t0,
      costUsd: cost, tokensIn, tokensOut, delta: delta.delta, savedBytes: delta.saved,
      governor: gov, compacted: compact.compacted, receipt, usage: prov.usage,
    };
    if (opts.zeroTrust) result.zeroTrust = mindZeroTrustPackage(result);
    return result;
  }

  /** S23/S106/S118: חימום ספקולטיבי — governor + תקרת תקציב כ-receipt */
  async mindPrefetch(prompts, opts = {}) {
    const list = Array.isArray(prompts) ? prompts : [prompts];
    const stats = this.mindStats?.() || {};
    const cfg = this._mindCfg || MIND;
    const budget = budgetPrefetchGate(stats, cfg, this._capsuleGenome || 'full', 0, opts);
    if (!budget.ok) return { n: 0, results: [], budget, skipped: true };
    const nMax = Math.min(opts.limit || 8, budget.allow, list.length);
    const cap = prefetchReceiptCapPlan(stats, cfg, nMax, opts);
    if (!cap.ok) return { n: 0, results: [], budget, cap, skipped: true };
    const out = [];
    for (const p of list.slice(0, nMax)) {
      try {
        out.push(await this.askMind(p, { ...opts, delta: false, signReceipt: false, skipBudget: true }));
      } catch (err) { out.push({ ok: false, error: err.message }); }
    }
    // S118: prefetch נספר בתקציב יומי
    try {
      const st = this._mindDayRoll();
      st.usd = +((st.usd || 0) + (cap.usd || 0)).toFixed(6);
      st.calls = (st.calls || 0) + out.length;
      saveState(this.state);
    } catch {}
    return { n: out.length, results: out, budget, cap };
  }

  /** S24: הגשת תשובה עם מירוץ מטמון / snapshot / חישוב */
  async latencyServe(name, producers, opts = {}) {
    const raced = await latencyRace(producers, opts);
    await this.webSet('latency:last', { name, winner: raced.winner, ms: raced.ms, ts: Date.now() }).catch(() => {});
    // EWMA לטלמטריה
    const st = this._mindDayRoll();
    st.latencyEwmaMs = st.latencyEwmaMs ? Math.round(st.latencyEwmaMs * 0.7 + raced.ms * 0.3) : raced.ms;
    saveState(this.state);
    return raced;
  }

  /** מעטפת נוחה: מירוץ cache mind מול קריאה חיה */
  async askMindRaced(input, opts = {}) {
    const cfg = { ...MIND, ...(this._mindCfg || {}) };
    const messages = mindNormalizeMessages(input, opts.system || cfg.system);
    const model = opts.model || cfg.model;
    const digest = mindPromptDigest(messages, model, { thread: opts.thread || 'default' });
    return this.latencyServe('mind', [
      {
        name: 'cache',
        fn: async () => {
          const c = this.db.get('mind:cache', digest);
          if (c?.content && (!c.exp || c.exp > Date.now())) return { ok: true, content: c.content, source: 'cache', digest, model: c.model };
          return null;
        },
      },
      { name: 'provider', fn: async () => this.askMind(input, { ...opts, force: true }) },
    ], opts);
  }

  /** S30: זיקוק תשובת LLM למדיניות/אתר-חי (∇ עתידי בלי ספק) */
  async echoDistill(name, content, opts = {}) {
    const slug = slugify(name) || 'distill';
    const dag = {
      nodes: {
        text: { value: String(content).slice(0, 8000) },
        out: { op: 'formula', args: { expr: 'x', vars: { x: { $ref: 'text' } } } },
      },
      output: 'out',
    };
    const pub = await this.publishSite(slug, dag);
    await this.webSet('distill:' + slug, { dagId: pub.dagId, digest: opts.digest || null, ts: Date.now() }).catch(() => {});
    return pub;
  }

  /** S27: אוטופיילוט ניטור — רץ מ-heal */
  async runTelemetryAutopilot(pulse) {
    const plan = telemetryAutopilotPlan(pulse || this._pulse(), this.mindStats(), this._skydb?.health?.());
    this._lastAutopilot = plan;
    for (const a of plan.actions.slice(0, 3)) {
      try {
        if (a.op === 'checkpoint') await this.publishDbCheckpoint();
        else if (a.op === 'prefer_cache') {
          this._mindCfg = { ...(this._mindCfg || MIND), preferCache: true };
          await this.webSet('mind:governor', { mode: 'prefer_cache', ts: Date.now() });
        } else if (a.op === 'mind_cache_only') {
          this._capsuleGenome = this._capsuleGenome || 'degraded';
          await this.webSet('mind:governor', { mode: 'cache-only', ts: Date.now() });
        } else if (a.op === 'freeze') await this.freezePinOffline({ reason: 'autopilot' });
        else if (a.op === 'warmup_mind' && this._mindWarmList?.length) await this.mindPrefetch(this._mindWarmList, { mock: !this._mindApiKey(this._mindCfg || MIND), signReceipt: false });
      } catch {}
    }
    await this.webSet('phi:autopilot', plan).catch(() => {});
    return plan;
  }

  /** S28: Intent Hot-Reload — דופק Φ מעדכן system prompt דינמי ל-Mind */
  async bindMindToPulse(opts = {}) {
    this._mindPulseBind = true;
    this._mindPulseOpts = opts;
    return { ok: true };
  }

  enableMindApi(opts = {}) {
    if (this._mindApiEnabled) return this;
    this._mindApiEnabled = true;
    if (opts.enabled || MIND.enabled) this.configureMind({ enabled: true, ...opts });
    const json = (body, status = 200) => ({ status, headers: { 'content-type': 'application/json' }, body });
    const read = async (req) => {
      try { return typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || req.body || '{}'); }
      catch { return {}; }
    };

    this.route('GET', '/api/mind/health', async () => json({
      mind: true, enabled: !!(this._mindCfg || MIND).enabled, stats: this.mindStats(),
      provider: (this._mindCfg || MIND).provider, model: (this._mindCfg || MIND).model,
      hasKey: !!this._mindApiKey(this._mindCfg || MIND), autopilot: this._lastAutopilot || null, ts: Date.now(),
    }));

    this.route('POST', '/api/mind/ask', async (req) => {
      const b = await read(req);
      try {
        const out = await this.askMind(b.messages || b.prompt || b.input || b, b);
        return json(out, out.ok === false && out.denied ? 403 : 200);
      } catch (err) { return json({ error: err.message }, 400); }
    });

    this.route('POST', '/api/mind/ask-raced', async (req) => {
      const b = await read(req);
      try { return json(await this.askMindRaced(b.messages || b.prompt || b.input || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });

    this.route('POST', '/api/mind/prefetch', async (req) => {
      const b = await read(req);
      try { return json(await this.mindPrefetch(b.prompts || b.prompt || [], b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });

    this.route('GET', '/api/mind/stats', async () => json(this.mindStats()));
    log('Μ/SkyMind: Ω API — /api/mind/ask · ask-raced · prefetch · stats (∇→Δ→cache→LLM).');
    this.enablePhysicsLatticeApi(opts);
    return this;
  }

  // ═══════════ S31–S36 Apparent Physics Lattice ═══════════

  /** S33: כתובת מלאה — קנונית + SkyHandle + virtual suffix + calling-card + דומיין */
  async ensureAddressStack(opts = {}) {
    ensureId(); deriveKeys();
    const handle = opts.handle || this.state.skyHandle || SITE_SLUG;
    let h = null;
    try { h = await this.publishHandle(handle); } catch { h = skyHandleOf(handle); }
    let virtual = null;
    if (opts.virtual !== false && SKY_SUFFIX_ENABLED) {
      try { virtual = await this.publishVirtualAddress({ handle: h.handle || handle, suffix: opts.suffix }); }
      catch { virtual = virtualGlobalAddressPlan({ slug: h.handle || handle, canonical: globalAddress(), npub: npub(), pubkey: keys.pubkey }); }
    }
    let card = this.state.callingCard || null;
    if (opts.card !== false && !card) {
      try { card = (await this.publishCallingCard({ handle: h.handle || handle })).url; } catch {}
    }
    const plan = addressStackPlan({
      canonical: globalAddress(),
      sky: h.sky || ('sky://' + slugify(handle)),
      at: (h.atUrl && h.atUrl()) || (globalAddress().replace(/\/$/, '') + '/@'),
      domain: CUSTOM_DOMAIN || null,
      callingCard: card,
      mirrors: mirrorAddresses(),
      virtual: virtual && virtual.ok !== false ? virtual : null,
      npub: npub(),
      slug: slugify(handle),
    });
    this.state.addressStack = plan;
    saveState(this.state);
    await this.webSet('address:stack', plan).catch(() => {});
    if (CUSTOM_DOMAIN) printDomainSetup();
    const shortLbl = plan.short ? ` · short=${plan.short}` : '';
    log(`Address stack: canon=${plan.canonical}${shortLbl} · ${plan.sky} · card=${card ? 'yes' : 'no'} · domain=${plan.domain || '—'}`);
    return plan;
  }

  /** S32: heartbeat עובד + עדכון peers ל-SkyDB */
  async beatWorker(extra = {}) {
    ensureId(); deriveKeys();
    const health = this._skydb?.health?.() || {};
    const beat = {
      id: DEVICE,
      pubkey: keys?.pubkey || null,
      opsPerSec: health.opsPerSec || 0,
      lagMs: health.lagMs || 0,
      shards: health.shards || 0,
      engine: health.engine || null,
      node: !!(this._dbApiEnabled || this._fullNodeServer),
      fullNode: !!this._fullNodeServer,
      ts: Date.now(),
      ...extra,
    };
    // S113 Shard Attest Gossip — canary workers מפרסמים attest ב-beat
    try {
      const cMeta = this.webGet('capsule:canary') || this.state.capsuleCanary;
      const gossip = shardAttestGossipPlan(cMeta, beat, { localId: DEVICE });
      if (gossip.gossip) {
        beat.attestDigest = gossip.attestDigest;
        beat.capsuleDigest = beat.capsuleDigest || gossip.attestDigest;
        beat.shardAttestGossip = true;
      }
    } catch {}
    this.state.workers ??= {};
    this.state.workers[DEVICE] = beat;
    // משוך registry מ-webkv ומתמזג (כולל S103 visitor tab workers)
    const remote = this.webGet('mesh:workers') || {};
    const merged = { ...remote, ...this.state.workers, [DEVICE]: beat };
    // ניקוי מתים
    const now = Date.now();
    for (const [id, w] of Object.entries(merged)) {
      if (!w?.ts || now - w.ts > WORKER_TTL_MS * 2) delete merged[id];
    }
    // S99/S111: health badge + republish skylive כש-workersAlive משתנה
    try {
      const sla = slaScoreFromWorkers(merged, now);
      const badge = liveAddressHealthBadge(sla);
      const prev = this.webGet('phi:health-badge') || this.state.healthBadge || null;
      const rep = healthBadgeRepublishGate(prev, badge);
      await this.webSet('phi:health-badge', badge).catch(() => {});
      this.state.healthBadge = badge;
      if (rep.republish && (this._livingAddress || LIVING_ADDRESS)) {
        this._pendingSkyliveRepublish = true;
        await this.webSet('skylive:republish', { ...rep, ts: now }).catch(() => {});
      }
    } catch {}
    this.state.workers = merged;
    saveState(this.state);
    await this.webSet('mesh:workers', merged).catch(() => {});
    await this.publishName('worker:' + DEVICE, 'beat:' + beat.ts).catch(() => {});
    // S9: peers חיים → SkyDB
    const peers = meshPeersFromRegistry(merged, now);
    if (peers.length) {
      this._skydb.configure({ peers, author: DEVICE });
      if (ENFORCE_SHARD_OWNER && peers.length >= 2) this._skydb._enforceOwner = true;
    }
    return beat;
  }

  async syncMeshPeers() {
    const remote = this.webGet('mesh:workers') || this.state.workers || {};
    this.state.workers = remote;
    const peers = meshPeersFromRegistry(remote);
    if (peers.length) this._skydb.configure({ peers, author: DEVICE });
    if (ENFORCE_SHARD_OWNER && peers.length >= 2) this._skydb._enforceOwner = true;
    else this._skydb._enforceOwner = false;
    return { peers, n: peers.length };
  }

  /**
   * S32: כתיבה מרובת-shards — מקביליות מקומית; תפוקה נראית = ops×owners
   * (פיזיקה: ממסרים לא מקבלים כל INSERT — רק checkpoint)
   */
  async meshBatch(ops, opts = {}) {
    await this.syncMeshPeers();
    const peers = this._skydb.peers || [DEVICE];
    const mine = [];
    const foreign = [];
    for (const op of ops || []) {
      const key = op.key;
      const own = this._skydb.shardOf(key);
      if (!own.owner || own.owner === DEVICE || peers.length < 2) mine.push(op);
      else foreign.push({ ...op, owner: own.owner, shard: own.shard });
    }
    const results = mine.length ? await this._skydb.batch(mine, { ...opts, force: opts.force }) : [];
    if (opts.checkpoint !== false) await this.publishDbCheckpoint().catch(() => {});
    const thr = apparentWriteThroughput(this._skydb.health().opsPerSec, Math.max(1, peers.length));
    return { local: results.length, redirected: foreign, throughput: thr, peers };
  }

  /** S34: פרסום מפת CDN לכתובת הגלובלית */
  async publishCdnLattice() {
    await this.ready?.().catch?.(() => {});
    ensureId(); deriveKeys();
    const h = skyHandleOf(this.state.skyHandle || SITE_SLUG);
    const mirrors = cdnMirrorList({
      npub: npub(),
      domain: CUSTOM_DOMAIN || null,
      handleAt: h.atUrl(),
      callingCard: this.state.callingCard || null,
    });
    const body = {
      v: 1, project: SITE_SLUG, canon: globalAddress(), sky: h.sky,
      mirrors, blossom: BLOSSOM_SERVERS.slice(), gateways: NSITE_GATEWAYS.slice(),
      ts: Date.now(),
    };
    this.state.cdnLattice = body;
    saveState(this.state);
    await this.webSet('cdn:lattice', body).catch(() => {});
    // העלאה כקובץ אתר אם אפשר
    try {
      const buf = Buffer.from(JSON.stringify(body, null, 2), 'utf8');
      const { hash } = await blossomUpload(buf, 'application/json');
      this.state.blobs ??= {}; this.state.blobs[hash] = true;
      // יתווסף בפרסום הבא — גם webkv מספיק ל-SDK
      this._cdnBlobHash = hash;
    } catch {}
    return body;
  }

  /** S34: בדיקת מראות — מירוץ HEAD/GET קצר */
  async probeCdnLattice(opts = {}) {
    const lattice = this.state.cdnLattice || await this.publishCdnLattice();
    const urls = (lattice.mirrors || []).filter((m) => m.kind === 'nsite' || m.kind === 'domain').map((m) => m.url);
    const results = [];
    await Promise.all(urls.slice(0, opts.limit || 5).map(async (url) => {
      const t0 = Date.now();
      try {
        const ctrl = new AbortController();
        const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs || 4000);
        const res = await fetch(url.replace(/\/$/, '') + '/skylive.json', { method: 'GET', signal: ctrl.signal, cache: 'no-store' }).catch(() => null);
        clearTimeout(timer);
        results.push({ url, ok: !!(res && res.ok), ms: Date.now() - t0, status: res?.status || 0 });
      } catch (err) {
        results.push({ url, ok: false, ms: Date.now() - t0, error: err.message });
      }
    }));
    results.sort((a, b) => (a.ok === b.ok ? a.ms - b.ms : (b.ok - a.ok)));
    // S104 Domain→Mesh Failover + S116 Sticky TTL
    let failover = domainMeshFailoverPlan(results, lattice.mirrors || [], opts);
    const prevSticky = this.state.cdnFailover || this.webGet('cdn:failover') || null;
    const sticky = failoverStickyTtlPlan(
      failover.failover ? { ...failover, stickyUntil: prevSticky?.stickyUntil || (Date.now() + (opts.stickyMs ?? 300_000)) } : prevSticky,
      Date.now(),
      opts,
    );
    if (sticky.preferNsite && sticky.sticky) {
      const nsiteFirst = domainMeshFailoverPlan(
        results.map((r) => ({ ...r, ok: lattice.mirrors?.find((m) => m.url === r.url)?.kind === 'domain' ? false : r.ok })),
        lattice.mirrors || [],
        opts,
      );
      failover = { ...failover, ...nsiteFirst, sticky };
      if (nsiteFirst.order?.length) failover.order = nsiteFirst.order;
    } else {
      failover = { ...failover, sticky };
    }
    const report = {
      best: results.find((r) => r.ok) || null, results, ts: Date.now(),
      failover, primary: failover.primary, mirrorOrder: failover.order, sticky,
    };
    // עדכון שער מועדף — הכתובת הקנונית המודפסת תעדיף שער שנגיש (לא אחד חסום אצל רבים)
    try {
      const bestUrl = report.best?.url || failover.primary?.url || null;
      if (bestUrl) {
        const host = String(bestUrl).replace(/^https?:\/\//, '').split('/')[0];
        const gw = NSITE_GATEWAYS.find((g) => host === g || host.endsWith('.' + g));
        if (gw && this.state.preferredGateway !== gw) {
          this.state.preferredGateway = gw;
          saveState(this.state);
          log('שער מועדף עודכן (נגד חסימות):', gw);
        }
      }
    } catch {}
    if ((failover.failover || sticky.preferNsite) && failover.order?.length) {
      const next = {
        ...lattice, mirrors: failover.order, failover: true,
        stickyUntil: sticky.stickyUntil || null, ts: Date.now(),
      };
      this.state.cdnLattice = next;
      this.state.cdnFailover = { failover: true, stickyUntil: sticky.stickyUntil, ts: Date.now() };
      await this.webSet('cdn:lattice', next).catch(() => {});
      await this.webSet('cdn:failover', this.state.cdnFailover).catch(() => {});
    }
    await this.webSet('cdn:probe', report).catch(() => {});
    this.state.cdnProbe = report;
    saveState(this.state);
    return report;
  }

  /** מוכנות פרסום מלא + סנכרון משתמשי קצה (לתצוגת --status / סוכנים) */
  publishReadiness(opts = {}) {
    const plan = inferProjectPublishPlan(ROOT, opts);
    const native = inferNativeClientPlan(ROOT, opts);
    plan.native = {
      artifacts: native.artifacts.length, shells: native.shells.map((s) => s.kind),
      hasLogin: native.login.hasLogin, localFiles: native.localImport.n, ready: native.ready,
    };
    this.state.publishPlan = { siteResolved: plan.siteResolved, build: plan.build, ready: plan.ready, native: plan.native, ts: Date.now() };
    try { saveState(this.state); } catch {}
    return plan;
  }

  /** תוכנית APK/EXE / הטמעה / ערוץ עדכונים */
  nativeClientPlan(opts = {}) {
    return inferNativeClientPlan(ROOT, opts);
  }

  /**
   * כותב scaffold הטמעה לפרויקט (מאחורי הקלעים):
   * skycloud-native.json + native/skycloud-bridge.js — WebView/sidecar קוראים אותם.
   */
  embedClientScaffold(opts = {}) {
    ensureId();
    if (!keys) deriveKeys();
    const native = inferNativeClientPlan(ROOT, opts);
    const address = (() => { try { return this.address(); } catch { return globalAddress(); } })();
    const meta = {
      v: 1,
      kind: 'skycloud-native-embed',
      project: PROJECT_NAME,
      slug: SITE_SLUG,
      address,
      mirrors: PUBLIC_WEBSITE ? mirrorAddresses() : [],
      api: {
        health: '/api/health',
        ux: '/api/ux/sync',
        update: '/api/client/update',
        loginSync: '/api/client/login-sync',
        publish: '/api/client/plan',
      },
      login: native.login,
      loginGate: native.loginGate,
      livingNotStatic: native.livingNotStatic,
      dualChannel: native.dualChannel,
      embedProjectSyncId: !!opts.embedProjectSync,
      // SYNC_ID רק אם המפעיל ביקש במפורש (לקוח סנכרון פרטי) — לא ברירת מחדל לחנויות
      syncId: opts.embedProjectSync ? syncId : null,
      sdk: '/cloud-web.js',
      writtenAt: Date.now(),
      honest: native.honest,
    };
    meta.digest = hex(sha256(Buffer.from('native-embed:v1:' + xiCanon({
      address: meta.address, slug: meta.slug, embedId: !!meta.syncId,
    })))).slice(0, 32);

    const outJson = path.join(ROOT, opts.manifestName || 'skycloud-native.json');
    const bridgeDir = path.join(ROOT, opts.bridgeDir || 'native');
    const bridgePath = path.join(bridgeDir, 'skycloud-bridge.js');
    const bridgeSrc = `/* SkyCloud native bridge — generated; load from WebView/Electron/Capacitor */
(function (g) {
  async function loadMeta() {
    try {
      const r = await fetch((g.SKYCLOUD_NATIVE_URL || '') + '/skycloud-native.json', { cache: 'no-store' });
      if (r.ok) return r.json();
    } catch (e) {}
    try { return g.SKYCLOUD_NATIVE_META || null; } catch (e) { return null; }
  }
  async function afterLogin(user) {
    const meta = await loadMeta();
    if (!meta) return { ok: false, reason: 'no-native-meta' };
    const base = meta.address || '';
    const body = { userId: user && (user.id || user.uid || user.sub) || null, hasLogin: true };
    try {
      const r = await fetch(base.replace(/\\/$/, '') + (meta.api && meta.api.loginSync || '/api/client/login-sync'), {
        method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
      });
      const j = await r.json();
      try {
        const u = await fetch(base.replace(/\\/$/, '') + (meta.api && meta.api.update || '/api/client/update'), { cache: 'no-store' });
        j.update = await u.json();
      } catch (e) {}
      try {
        const x = await fetch(base.replace(/\\/$/, '') + (meta.api && meta.api.ux || '/api/ux/sync'), { cache: 'no-store' });
        j.ux = await x.json();
      } catch (e) {}
      return j;
    } catch (err) { return { ok: false, reason: err.message || String(err) }; }
  }
  async function checkUpdate() {
    const meta = await loadMeta();
    if (!meta || !meta.address) return { ok: false, reason: 'no-address' };
    const r = await fetch(meta.address.replace(/\\/$/, '') + '/api/client/update', { cache: 'no-store' });
    return r.json();
  }
  g.SkyCloudNative = { loadMeta: loadMeta, afterLogin: afterLogin, checkUpdate: checkUpdate };
})(typeof globalThis !== 'undefined' ? globalThis : this);
`;
    try {
      fs.writeFileSync(outJson, JSON.stringify(meta, null, 2), 'utf8');
      fs.mkdirSync(bridgeDir, { recursive: true });
      fs.writeFileSync(bridgePath, bridgeSrc, 'utf8');
    } catch (err) {
      return { ok: false, error: err.message, meta, native };
    }
    this.state.nativeEmbed = { path: outJson, bridge: bridgePath, digest: meta.digest, ts: Date.now() };
    saveState(this.state);
    this.webSet('client:native-embed', { digest: meta.digest, address, ts: Date.now() }).catch(() => {});
    return { ok: true, manifestPath: outJson, bridgePath, meta, native, steps: native.steps };
  }

  /** ייבוא קבצי data/local מקומיים לענן הפרטי */
  async importLocalData(opts = {}) {
    const plan = localDataImportPlan(ROOT, opts);
    const imported = [];
    for (const f of plan.files) {
      try {
        const buf = await fsp.readFile(path.join(ROOT, f.rel));
        const ing = sandboxIngest(buf);
        if (ing.suspicious && !opts.allowSuspicious) {
          cryptoShred(buf);
          imported.push({ key: f.key, rel: f.rel, skipped: true, reason: 'sandbox-' + ing.kind });
          continue;
        }
        let value;
        if (/\.json$/i.test(f.rel)) {
          try { value = JSON.parse(buf.toString('utf8')); }
          catch { value = { _raw: buf.toString('utf8'), _parseError: true }; }
        } else if (/\.(txt|csv)$/i.test(f.rel)) {
          value = { text: buf.toString('utf8') };
        } else {
          value = { encoding: 'base64', bytes: buf.toString('base64'), size: buf.length };
        }
        await this.set(f.key, { ...value, _importRel: f.rel, _importedAt: Date.now() });
        imported.push({ key: f.key, rel: f.rel, size: f.size });
      } catch (err) {
        imported.push({ key: f.key, rel: f.rel, error: err.message });
      }
    }
    const report = { ok: true, n: imported.length, imported, plan: { n: plan.n, policy: plan.policy } };
    this.state.localImport = report;
    saveState(this.state);
    await this.webSet('client:local-import', { n: report.n, ts: Date.now() }).catch(() => {});
    return report;
  }

  /**
   * פרסום ערוץ עדכונים ללקוחות מותקנים (APK/EXE שבתיקיית הפרויקט).
   * מעלה ארטיפקטים ל-Blossom + מניפסט ל-webkv / קובץ מקומי — נפרד מ־--deploy של האתר.
   */
  async publishClientUpdate(opts = {}) {
    ensureId();
    if (!keys) deriveKeys();
    const native = inferNativeClientPlan(ROOT, opts);
    const arts = [];
    for (const a of native.artifacts) {
      try {
        const buf = await fsp.readFile(path.join(ROOT, a.rel));
        const digest = hex(sha256(buf));
        let url = null;
        if (opts.upload !== false) {
          try {
            const up = await blossomUpload(buf, mimeOf(a.name) || 'application/octet-stream');
            url = `${up.servers[0]}/${up.hash}`;
          } catch (err) {
            log('אזהרה: העלאת ארטיפקט לקוח נכשלה:', a.rel, err.message);
          }
        }
        arts.push({ ...a, sha256: digest, url });
      } catch (err) {
        arts.push({ ...a, error: err.message });
      }
    }
    const tip = this.state.immortalTip?.digest || this.state.fullAppMerkle?.digest || this.webGet('immortal:tip')?.digest || null;
    const planned = clientUpdateManifestPlan({
      version: opts.version || opts.semver || String(this.state.clientUpdate?.manifest?.version || '1.0.0'),
      tipDigest: tip,
      siteAddress: this.address(),
      channel: opts.channel || 'stable',
      artifacts: arts.filter((a) => a.sha256),
      loginSync: native.loginGate,
      fullAppStrict: FULL_APP_STRICT,
      now: Date.now(),
    });
    // bump patch if same version republish
    if (opts.bump !== false && this.state.clientUpdate?.manifest?.digest === planned.manifest.digest) {
      /* keep */
    }
    this.state.clientUpdate = { manifest: planned.manifest, ts: Date.now() };
    saveState(this.state);
    await this.webSet('client:update', planned.manifest).catch(() => {});
    try {
      const p = path.join(ROOT, opts.out || 'skycloud-client-update.json');
      fs.writeFileSync(p, JSON.stringify(planned, null, 2), 'utf8');
      planned.path = p;
    } catch (err) {
      planned.writeError = err.message;
    }
    // גם באתר הציבורי אם אפשר — נתיב קבוע לבדיקת עדכון
    try {
      const siteDir = resolveSiteDir();
      if (siteDir) {
        const dest = path.join(ROOT, siteDir, 'skycloud-client-update.json');
        fs.writeFileSync(dest, JSON.stringify(planned.manifest, null, 2), 'utf8');
        planned.sitePath = dest;
      }
    } catch {}
    return { ok: true, ...planned, artifacts: arts, nativeSummary: { shells: native.shells, login: native.login.hasLogin } };
  }

  /** חוזה סנכרון אחרי login (ל־Ω / WebView) */
  clientLoginSync(opts = {}) {
    const native = inferNativeClientPlan(ROOT, opts);
    const gate = clientLoginSyncGate({
      hasLogin: opts.hasLogin != null ? !!opts.hasLogin : (native.login.hasLogin || !!opts.userId),
      userId: opts.userId,
      embedProjectSync: !!opts.embedProjectSync,
    });
    return {
      ...gate,
      ux: this.syncUxStatus({ lang: opts.lang || 'he' }),
      update: this.state.clientUpdate?.manifest || this.webGet('client:update') || null,
      address: (() => { try { return this.address(); } catch { return null; } })(),
      livingNotStatic: native.livingNotStatic,
      latticeWave: {
        hybridLattice: !!(typeof HYBRID_LATTICE !== 'undefined' && HYBRID_LATTICE),
        bftSync: !!(typeof BFT_SYNC !== 'undefined' && BFT_SYNC),
        zkHint: 'POST zkProve transcript for capability proof (S154)',
      },
    };
  }

  /** מצב UX לסנכרון — הודעות לאדם + יכולת כתיבה לרשת */
  syncUxStatus(opts = {}) {
    let relaysUp = 0;
    try {
      const rep = this.pool?.healthReport?.() || [];
      relaysUp = Array.isArray(rep) ? rep.length : 0;
    } catch { relaysUp = this.webGet('phi:health')?.relaysUp ?? 0; }
    const phi = this.webGet('phi:health') || {};
    const ux = relayOutageUxPlan({ relaysUp: relaysUp || phi.relaysUp || 0 }, opts);
    const deg = zeroPeerDegradedUiPlan({
      workersAlive: phi.workersAlive ?? 0,
      meshAlive: (phi.workersAlive ?? 0) > 0 || relaysUp > 0,
      hasSnap: !!this.webGet('living:snaps'),
      snapFresh: true,
    });
    return {
      ...ux,
      zeroPeer: deg,
      vitality: phi.vitality ?? null,
      scenario: phi.scenario ?? null,
      address: (() => { try { return this.address(); } catch { return null; } })(),
      trustServer: false,
    };
  }

  /** ערכת שחזור בלי SYNC_ID — לזיהוי ענן בלבד */
  exportRecoveryKit(opts = {}) {
    ensureId();
    if (!keys) deriveKeys();
    const plan = recoveryKitPlan({
      pubkey: keys.pubkey,
      address: this.address(),
      tipDigest: this.state.immortalTip?.digest || this.webGet('immortal:tip')?.digest || null,
      siteSlug: SITE_SLUG,
      flockId: this.state.flock?.flockId || null,
      now: Date.now(),
    });
    this.state.recoveryKit = plan.kit;
    saveState(this.state);
    this.webSet('recovery:kit', plan.kit).catch(() => {});
    if (opts.writeFile !== false) {
      try {
        const p = path.join(ROOT, 'skycloud-recovery-kit.json');
        fs.writeFileSync(p, JSON.stringify(plan, null, 2), 'utf8');
        plan.path = p;
      } catch (err) {
        plan.writeError = err.message;
      }
    }
    return plan;
  }

  /** התראת מפעיל — Telegram ו/או webhook (ENV). כנה: דורש הגדרה; לא שולח בלי מפתח. */
  async notifyOpsAlert(kind, detail = {}, opts = {}) {
    const cfg = { ...ALERTS, ...(opts.alerts || {}) };
    const enabled = cfg.enabled || !!(cfg.webhookUrl || (cfg.telegramBotToken && cfg.telegramChatId));
    if (!enabled && !opts.force) {
      return { ok: false, reason: 'alerts-disabled', hint: 'Set SKYCLOUD_ALERT_WEBHOOK or SKYCLOUD_TG_BOT+SKYCLOUD_TG_CHAT' };
    }
    const ev = alertEventPlan(kind, { ...detail, device: DEVICE }, opts);
    const throttle = alertThrottleGate(this.state.lastAlertTs, Date.now(), cfg.minIntervalMs);
    if (!throttle.send && !opts.force) return { ok: false, reason: throttle.reason, waitMs: throttle.waitMs, event: ev };
    const text = `[SkyCloud ${ev.kind}] ${ev.code || ''} ${ev.message} relaysUp=${ev.relaysUp ?? '?'} dig=${ev.digest}`.slice(0, 900);
    const results = [];
    try {
      if (cfg.webhookUrl) {
        const res = await fetch(cfg.webhookUrl, {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({ text, event: ev, source: 'skycloud' }),
        });
        results.push({ channel: 'webhook', status: res.status, ok: res.ok });
      }
      if (cfg.telegramBotToken && cfg.telegramChatId) {
        const url = `https://api.telegram.org/bot${cfg.telegramBotToken}/sendMessage`;
        const res = await fetch(url, {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({ chat_id: cfg.telegramChatId, text }),
        });
        results.push({ channel: 'telegram', status: res.status, ok: res.ok });
      }
    } catch (err) {
      return { ok: false, reason: err.message, event: ev };
    }
    if (!results.length) return { ok: false, reason: 'no-channel-configured', event: ev };
    this.state.lastAlertTs = Date.now();
    saveState(this.state);
    await this.webSet('alert:last', { event: ev, results, ts: Date.now() }).catch(() => {});
    return { ok: results.some((r) => r.ok), event: ev, results };
  }

  /** S36: דוח SRE/SLA פנימי (+ S115/S128 visitor weight governance) */
  slaReport() {
    const workers = this.state.workers || this.webGet('mesh:workers') || {};
    const raw = slaScoreFromWorkers(workers);
    const now = Date.now();
    const visSample = Object.values(workers).find((w) => w?.visitor || String(w?.id || '').startsWith('v:'));
    const vw = visitorWeightGovPlan({
      trust: visSample?.trust ?? 0.5,
      sla: raw.score ?? 50,
      beatAgeMs: visSample ? now - (visSample.ts || now) : 60_000,
    });
    const visitorCap = visitorBeatTtlCapPlan(workers, { visitorWeight: vw.weight });
    const sla = {
      ...raw,
      workersAliveRaw: visitorCap.workersAliveRaw,
      nodesAlive: visitorCap.nodesAlive,
      visitorsAlive: visitorCap.visitorsAlive,
      weightedAlive: visitorCap.weightedAlive,
      target: visitorCap.target,
      score: visitorCap.nodesAlive === 0
        ? Math.min(raw.score, 35)
        : raw.score,
      visitorCap,
      visitorWeight: vw,
    };
    const cont = this.state.continuityTip || this.webGet('continuity:tip');
    const thr = apparentWriteThroughput(this._skydb?.health?.()?.opsPerSec || 0, Math.max(1, visitorCap.nodesAlive || 1));
    return {
      sla, continuity: !!cont, genome: this._capsuleGenome || 'full',
      address: this.state.addressStack || null,
      cdnBest: this.state.cdnProbe?.best || null,
      throughput: thr,
      db: this._skydb?.health?.() || null,
      mind: this.mindStats?.() || null,
      visitorCap,
      visitorWeight: vw,
      space: this.state.spaceMesh || null,
      ado: this.state.ado || null,
      organism: this.webGet('ado:quorum') || null,
      ts: Date.now(),
    };
  }

  /**
   * SpaceMesh-Ν — DTN + CGR + orbital dance (NASA-grade control plane).
   * כנה: לא משגר לוויינים; מרקד בין נתיבים terrestrial/space ומחזיק custody כשהמסלול חשוך.
   */
  async enableSpaceMesh(opts = {}) {
    if (!SPACE_MESH.enabled && !opts.force) {
      return { ok: false, reason: 'space-mesh-disabled' };
    }
    this._spaceMesh = true;
    const now = Date.now();
    const terr = (this.pool?.healthyUrls?.() || RELAYS).slice(0, 10);
    const space = opts.spaceClass || SPACE_MESH.spaceClass || [];
    const dance = satelliteDancePlan(terr, space, now, opts);
    const plan = buildOrbitalContactPlan({ now, ...opts });
    // CGR: ground → best visible orb → ground (round-trip diversity)
    const bestOrb = dance.visibleSlots?.[0] != null ? ('orb:' + dance.visibleSlots[0]) : 'orb:0';
    let route = contactGraphRoute(plan, 'ground', bestOrb, { now });
    if (route.ok) {
      const back = contactGraphRoute(plan, bestOrb, 'ground', { now: now + (route.delayMs || 0) });
      if (back.ok) route = { ok: true, path: [...route.path, ...back.path.slice(1)], delayMs: (route.delayMs || 0) + (back.delayMs || 0), hops: (route.hops || 0) + (back.hops || 0) };
    }
    const presence = spacePresencePlan({
      canon: globalAddress(),
      tip: this.state.immortalTip?.digest || this.webGet('immortal:tip')?.digest || null,
      flockId: this.state.flock?.flockId || null,
      living: !!(this._livingAddress || LIVING_ADDRESS),
      probes: this.state.antennaProbes || [],
      spaceClass: space,
    });
    const pin = spaceAddressPinGate(presence, {});
    const manifest = spaceMeshManifest(dance, route, {
      now, ado: this.state.ado || null, vaultRoot: this.state.vaultTips ? Object.keys(this.state.vaultTips)[0] : null,
      ecMode: 'eventual-slow', spacePresence: presence,
    });
    manifest.spacePresence = presence;
    manifest.connectNotControl = true;
    manifest.addressInSpace = !!presence.addressInSpace;
    // הכנסת ממסרי space ל-fallback hopping לגיוון — חיבור, לא שליטה
    if (this.pool && space.length) {
      for (const u of space) {
        if (!this.pool.fallback.includes(u) && !this.pool.active.has(u)) this.pool.fallback.push(u);
      }
      for (const a of dance.active.filter((x) => x.segment === 'space').slice(0, 3)) {
        try { this.pool.active.add(a.url); this.pool._connect?.(a.url); } catch {}
      }
    }
    this.state.spaceMesh = manifest;
    this.state.spaceDance = dance;
    this.state.spaceContacts = { n: plan.nContacts, now };
    this.state.spacePresence = presence;
    saveState(this.state);
    await this.webSet('space:mesh', manifest).catch(() => {});
    await this.webSet('space:presence', presence).catch(() => {});
    await this.webSet('space:dance', { phase: dance.phase, diversity: dance.diversity, alwaysOn: dance.alwaysOn, ts: now }).catch(() => {});
    // מפרסמים tip כתובת גם על נתיב space (שם חי ברשת)
    await this.publishName('space-address', 'space:' + (presence.digest || 'live')).catch(() => {});
    await this.beatWorker({
      spaceMesh: true, dancePhase: dance.phase, diversity: dance.diversity?.score,
      spacePresence: true, addressInSpace: presence.addressInSpace,
    }).catch(() => {});
    // קטלוג מזהי לוויינים לוגיים + אופציונלית שרשרת hop
    const catalog = discoverSpaceCatalog({ spaceClass: space, dance, probes: this.state.antennaProbes || [] });
    this.state.spaceCatalog = catalog;
    await this.webSet('space:catalog', catalog).catch(() => {});
    let hops = null;
    if (opts.hop !== false && catalog.n > 0) {
      hops = await this.spaceHopReplicate({ catalog, tip: presence.tip, replicateSelf: false, maxHops: opts.maxHops || 6 }).catch(() => null);
    }
    log(`SpaceMesh: phase=${dance.phase} · addressInSpace=${presence.ok} · satIds=${catalog.n} · hops=${hops?.hopPlan?.hops || 0} · connect≠control`);
    return { ok: true, manifest, dance, route, presence, pin, catalog, hops, plan: { nContacts: plan.nContacts } };
  }

  /** מזהה צמתי־חלל (sat:…) וגולש/משכפל hop-by-hop דרך שכנים */
  async discoverSpaceNodes(opts = {}) {
    if (!this._spaceMesh && (SPACE_MESH.enabled || opts.force)) {
      await this.enableSpaceMesh({ force: true }).catch(() => {});
    }
    const dance = this.state.spaceDance || satelliteDancePlan(
      this.pool?.healthyUrls?.() || RELAYS, SPACE_MESH.spaceClass, Date.now(), opts,
    );
    const probes = opts.probes || this.state.antennaProbes || await this.probeNetworkAntennas({ limit: 8 }).catch(() => []);
    const catalog = discoverSpaceCatalog({
      spaceClass: SPACE_MESH.spaceClass, dance, probes, limit: opts.limit || 64,
    });
    this.state.spaceCatalog = catalog;
    saveState(this.state);
    await this.webSet('space:catalog', catalog).catch(() => {});
    this._adisPush?.({ type: 'space', msg: `catalog n=${catalog.n}` });
    return catalog;
  }

  /** שרשרת: מצא sat ID → שכפל → גלה שכן דרכו → שכפל הלאה */
  async spaceHopReplicate(opts = {}) {
    const catalog = opts.catalog || await this.discoverSpaceNodes(opts);
    const tip = opts.tip || this.state.immortalTip?.digest || this.webGet('immortal:tip')?.digest || null;
    const contactPlan = buildOrbitalContactPlan({ now: Date.now(), ...opts });
    const hopPlan = spaceHopReplicationPlan(catalog, {
      tipDigest: tip, maxHops: opts.maxHops || 8, startId: opts.startId, contactPlan,
    });
    // S138: quorum לפני שכפול (אלא אם skipQuorum)
    let quorum = null;
    if (opts.skipQuorum !== true) {
      const votesBySat = opts.votesBySat || {};
      for (const h of hopPlan.chain || []) {
        if (!votesBySat[h.id]) {
          // self + mesh peer attestations (logical)
          votesBySat[h.id] = [
            { peerId: 'self', satId: h.id },
            { peerId: 'mesh:' + (h.slot ?? 0), satId: h.id },
            ...(opts.extraVotes || []),
          ];
        }
      }
      quorum = spaceCatalogQuorumBatch(hopPlan, votesBySat, opts);
      if (!quorum.ok && opts.requireQuorum !== false) {
        return { ok: false, reason: quorum.reason, hopPlan, quorum, catalog };
      }
    }
    // S141: credit על crosslinks
    let creditGate = null;
    if (opts.skipCredit !== true) {
      const meter = resourceMeterSample(opts.meter || { hostSec: opts.hostSec || 30, bandwidthBytes: 1e6 });
      const credit = resourceCreditPlan(meter, opts);
      creditGate = hopChainCreditGate(hopPlan, credit, opts);
      if (!creditGate.ok && opts.requireCredit) {
        return { ok: false, reason: creditGate.reason, hopPlan, creditGate, catalog };
      }
    }
    const results = [];
    for (const hop of hopPlan.chain || []) {
      if (quorum?.allowed && !quorum.allowed.includes(hop.id) && opts.requireQuorum !== false && opts.skipQuorum !== true) {
        results.push({ ok: false, satId: hop.id, reason: 'quorum-skip' });
        continue;
      }
      const entry = {
        satId: hop.id, tip, url: hop.url, slot: hop.slot, hop: hop.hop,
        canon: globalAddress(), ts: Date.now(), connectNotControl: true,
      };
      this.state.spaceReplicas ??= {};
      this.state.spaceReplicas[hop.id] = entry;
      await this.webSet('space:replica:' + hop.id, entry).catch(() => {});
      await this.dtnSend({
        kind: 'space-hop-replica', satId: hop.id, tip, canon: globalAddress(), hop: hop.hop,
      }, { forceDeliver: !!this.state.spaceDance?.alwaysOn }).catch(() => {});
      results.push({ ok: true, ...entry });
      this._adisPush?.({ type: 'space-hop', msg: `hop${hop.hop} ${hop.id.slice(0, 16)} tip=${(tip || '').slice(0, 8)}` });
    }
    // S137–S141 + compositions
    const cont = await this.runSpaceHopContinuations({
      hopPlan, catalog, tip, creditGate, quorum, ...opts,
    }).catch((err) => ({ ok: false, error: err.message }));
    if (opts.replicateSelf !== false) {
      await this.replicateSelf({ skipPack: true, tip: this.state.immortalTip }).catch(() => {});
    }
    saveState(this.state);
    await this.webSet('space:hops', { plan: hopPlan, results, cont, quorum, creditGate, ts: Date.now() }).catch(() => {});
    await this.ensureSpaceAddressPresence({ tip, force: true }).catch(() => {});
    log(`SpaceHop: ${hopPlan.hops} hops · ids=${(hopPlan.visitedIds || []).map((x) => x.slice(0, 12)).join('→')}`);
    return { ok: hopPlan.ok, hopPlan, results, catalog, quorum, creditGate, cont };
  }

  /** S137–S141 + חמשת החיבורים — אחרי/עם שרשרת hop */
  async runSpaceHopContinuations(opts = {}) {
    const hopPlan = opts.hopPlan || this.webGet('space:hops')?.plan || null;
    const tip = opts.tip || hopPlan?.tip || this.state.immortalTip?.digest || null;
    const catalog = opts.catalog || this.state.spaceCatalog || await this.discoverSpaceNodes(opts).catch(() => ({ nodes: [] }));
    const canon = opts.canon || globalAddress();
    const receipts = addressOnHopReceiptChain(hopPlan || { chain: [] }, { tip, canon });
    if (receipts.ok) {
      this.state.spaceHopReceipts = receipts;
      await this.webSet('space:hop:receipts', receipts).catch(() => {});
      await this.webSet('skylive:hopReceipts', receipts.skylive).catch(() => {});
      for (const r of receipts.receipts || []) {
        if (r.receipt) await this.publishUiReceipt(r.receipt, { site: 'space-hop', output: { satId: r.satId, hop: r.hop } }).catch(() => {});
      }
    }
    let hopVault = null;
    if ((hopPlan?.hops || 0) >= (SPACE_MESH.hopVaultMin || 3) || opts.forceVault) {
      const blob = opts.vaultBlob || Buffer.from(xiCanon({ tip, canon, roost: hopPlan?.visitedIds || [] }));
      hopVault = hopVaultShardPlan(blob, hopPlan, { minHops: opts.minHops || SPACE_MESH.hopVaultMin || 3, k: opts.k || 2 });
      if (hopVault.ok) {
        this.state.hopVault = hopVault;
        await this.webSet('space:hop:vault', { digest: hopVault.digest, merkleRoot: hopVault.merkleRoot, placement: hopVault.placement }).catch(() => {});
        for (const p of hopVault.placement || []) {
          await this.webSet('space:vault:' + p.satId, { shareIndex: p.shareIndex, digest: p.digest, tip }).catch(() => {});
        }
      }
    }
    const presence = this.state.spacePresence || spacePresencePlan({
      canon, tip, living: true, immortal: true, probes: this.state.antennaProbes || [], spaceClass: SPACE_MESH.spaceClass,
    });
    const roam = livingAddressRoamSatPlan(presence, hopPlan, { canon, tip });
    if (roam.ok) {
      this.state.addressRoam = roam;
      await this.webSet('address:roam', roam).catch(() => {});
    }
    const flockJobs = flockHopSubMindPlan(
      opts.task || { kind: 'space-hop-mind', tip },
      hopPlan,
      this.state.workers || this.webGet('mesh:workers') || {},
      opts,
    );
    await this.webSet('space:hop:submind', { root: flockJobs.root, nLight: flockJobs.nLight, nHeavy: flockJobs.nHeavy, memoryPins: flockJobs.memoryPins }).catch(() => {});
    const dance = this.state.spaceDance || null;
    const pending = this._skydb?.walPending?.() || this.state.walPending || opts.walPending || [];
    const darkHeal = vaultDarkWindowHealPlan(
      opts.bundle || { id: 'hop-capsule', expire: Date.now() + (SPACE_MESH.custodyTtlMs || 3_600_000) },
      dance || { alwaysOn: true },
      pending,
      Object.keys(this.state.spaceReplicas || {}),
      { force: !!dance?.alwaysOn, contactOpen: opts.contactOpen },
    );
    if (darkHeal.heal && darkHeal.ship && this._skydb?.replayWalPending) {
      await this._skydb.replayWalPending().catch(() => {});
    }
    const did = this.state.did || didDocumentPlan({ handle: opts.handle || this.handle || 'sky', pubkey: this.npub });
    const drivePin = didSkyDriveHopPinPlan(did, opts.drivePath || '/space/roam.json', hopPlan || catalog, {
      blob: opts.driveBlob || Buffer.from(xiCanon({ tip, roam: roam.roosts || [], receipts: receipts.digest })),
      k: 2, n: Math.min(4, hopPlan?.hops || 3),
    });
    if (drivePin.ok) {
      await this.webSet('space:drive:pin', { path: drivePin.path, digest: drivePin.digest, pins: drivePin.pins, did: drivePin.did.id }).catch(() => {});
    }
    const store = this.state.adisVectors || this.webGet('adis:vec') || {};
    const recall = adisRecallOnHopPlan(opts.recallQuery || 'space hop memory', store, this.state.spaceReplicas || {}, opts);
    const meter = resourceMeterSample(opts.meter || { hostSec: 30, bandwidthBytes: 2e6 });
    const credit = resourceCreditPlan(meter, opts);
    const xiGate = xiSatEconomyGate(
      opts.dag || { nodes: { out: { value: tip || 1 } }, output: 'out' },
      this.state.antennaProbes || opts.probes || [{ ok: true, rttMs: 700, satelliteLikely: true }],
      credit,
      { requireSatellite: opts.requireSatellite !== false, bytes: 16_000 },
    );

    // S142 Roam→Skylive Autopublish
    const prevRoam = this.state.addressRoamPrev || this.webGet('address:roam:prev') || null;
    const roamPub = roamSkyliveAutopublishGate(prevRoam, roam, opts);
    if (roam.ok) {
      this.state.addressRoamPrev = { roamDigest: roam.roamDigest };
      await this.webSet('address:roam:prev', this.state.addressRoamPrev).catch(() => {});
    }
    if (roamPub.republish && opts.skipSkylive !== true) {
      this.state.skyliveRoamDirty = true;
      await this.webSet('skylive:roamDirty', { digest: roamPub.digest, ts: Date.now() }).catch(() => {});
    }

    // S143 Quorum Tip Cutover
    const votesBySat = opts.votesBySat || {};
    for (const r of (roam.roosts || [])) {
      if (!votesBySat[r.satId]) {
        votesBySat[r.satId] = [
          { peerId: 'self', satId: r.satId },
          { peerId: 'roost:' + r.hop, satId: r.satId },
        ];
      }
    }
    const tipCut = quorumTipCutoverGate(tip, roam, votesBySat, opts);
    if (tipCut.cutover) {
      await this.webSet('space:tip:cutover', tipCut).catch(() => {});
    }

    // S144 Hop-Vault Anti-Revision
    let vaultRev = null;
    let dualFreeze = null;
    if (hopVault?.ok) {
      vaultRev = hopVaultAntiRevisionGate(hopVault, this.state.hopVaultChainTip || null, opts);
      dualFreeze = dualRootHopFreezeGate(this.webGet('trust:root') || this.state.trustRootState || {}, hopVault, opts);
      if (vaultRev.ok && dualFreeze.ship) {
        this.state.hopVaultChainTip = { digest: hopVault.digest, prev: this.state.hopVaultChainTip?.digest || null };
        await this.webSet('space:hop:vault:tip', this.state.hopVaultChainTip).catch(() => {});
      } else if (!dualFreeze?.ship) {
        hopVault = { ...hopVault, shipBlocked: true, blockReason: dualFreeze?.reason };
      }
    }

    // S145 Hop Rain Stream → ADIS events
    const rainEvents = [
      ...(receipts.receipts || []).map((r) => ({
        type: 'hop-receipt', satId: r.satId, tip: r.tip, receiptId: r.receipt?.id, ts: Date.now(),
      })),
      ...((hopPlan?.chain || []).map((h) => ({ type: 'hop', satId: h.id, tip, ts: Date.now() }))),
    ];
    const rain = hopRainStreamPlan(rainEvents, { flockId: this.state.flock?.flockId });
    for (const ev of rain.events || []) {
      this._adisPush?.({ type: ev.type, msg: ev.msg, flockId: ev.flockId, pathClass: 'satellite-path', ts: ev.ts });
    }
    await this.webSet('space:hop:rain', { digest: rain.digest, n: rain.n, lines: rain.lines.slice(0, 32) }).catch(() => {});

    // S146 Crosslink Bandwidth Market
    const markets = [];
    for (const g of (opts.creditGate?.gates || [])) {
      if (g?.debitPlan) markets.push(crosslinkBandwidthMarketPlan(g.debitPlan, opts));
    }
    if (!markets.length && (hopPlan?.chain || []).length > 1) {
      const edge = {
        from: hopPlan.chain[0].id, to: hopPlan.chain[1].id, bandwidthBytes: 64_000, kind: 'crosslink',
      };
      markets.push(crosslinkBandwidthMarketPlan(hopBandwidthDebitPlan(edge, opts), opts));
    }
    await this.webSet('space:hop:market', { n: markets.length, markets: markets.slice(0, 16), ts: Date.now() }).catch(() => {});

    // S147 Ξ Receipt-on-Sat
    let xiRc = null;
    if (xiGate.ok && hopPlan?.chain?.[0]?.id) {
      xiRc = xiReceiptOnSatPlan(xiGate, hopPlan.chain[0].id, { result: true });
      if (xiRc.ok) {
        await this.publishUiReceipt(xiRc.receipt, { site: 'xi-sat', output: { satId: xiRc.satId, dagId: xiRc.dagId } }).catch(() => {});
        await this.webSet('space:xi:receipt', { id: xiRc.receipt.id, satId: xiRc.satId, dagId: xiRc.dagId }).catch(() => {});
      }
    }

    // S149 Flock Merge on Roosts
    const flockMembers = this.state.adoMembers || this.webGet('ado:members') || {};
    const roostMembers = Object.fromEntries(Object.entries(flockMembers).map(([id, m], i) => {
      const sat = m.satId || roam.roosts?.[i % Math.max(1, roam.roosts?.length || 1)]?.satId || hopPlan?.chain?.[0]?.id;
      return [id, { ...m, satId: sat, flockId: this.state.flock?.flockId, tipDigest: tip }];
    }));
    // ensure votes for roost members
    for (const m of Object.values(roostMembers)) {
      if (m.satId && !votesBySat[m.satId]) {
        votesBySat[m.satId] = [{ peerId: 'self', satId: m.satId }, { peerId: 'peer:' + m.satId.slice(-4), satId: m.satId }];
      }
    }
    const flockRoost = flockMergeOnRoostsPlan(roostMembers, roam, votesBySat, opts);

    // S150 Dark-Window Prefetch Cap
    const prefetchCap = darkWindowPrefetchCapPlan(
      darkHeal,
      this.state.mindStats || {},
      MIND,
      this.state.genome || 'full',
      opts.pendingPrefetch || 0,
      opts,
    );

    // Compositions
    const roamBadge = roamHealthBadgePlan(roam, slaScoreFromWorkers(this.state.workers || {}), {});
    const vaultHazard = hopVaultExtinctionHazardPlan(hopVault, this.state.extinctionHist || [], {});
    const xiSub = xiSubMindEconomyPlan(
      opts.task || { kind: 'space-hop-mind', tip },
      hopPlan,
      this.state.workers || this.webGet('mesh:workers') || {},
      credit,
      this.state.antennaProbes || opts.probes || [{ ok: true, rttMs: 700, satelliteLikely: true }],
      { requireSatellite: false, ...opts },
    );

    if (vaultHazard.preempt) {
      await this.replicateSelf({ skipPack: true, tip: this.state.immortalTip }).catch(() => {});
    }

    const out = {
      ok: true,
      s137: receipts,
      s138: opts.quorum || tipCut,
      s139: hopVault,
      s140: recall,
      s141: opts.creditGate || null,
      s142: roamPub,
      s143: tipCut,
      s144: vaultRev,
      s145: { digest: rain.digest, n: rain.n },
      s146: { n: markets.length, markets },
      s147: xiRc,
      s148: dualFreeze,
      s149: flockRoost,
      s150: prefetchCap,
      roam, flockJobs, darkHeal, drivePin, xiGate,
      roamBadge, vaultHazard, xiSub,
      tip, ts: Date.now(),
    };
    this.state.spaceHopCont = out;
    await this.webSet('space:hop:continuations', {
      ok: true, receipts: receipts.digest, roam: roam.roamDigest, vault: hopVault?.digest || null,
      drive: drivePin.digest || null, xi: xiGate.dagId || null,
      rain: rain.digest, tipCutover: tipCut.cutover, prefetch: prefetchCap.ok,
      ts: out.ts,
    }).catch(() => {});
    this._adisPush?.({ type: 'space-cont', msg: `S137-150 roam=${roam.n || 0} vault=${!!hopVault?.ok} rain=${rain.n}` });
    return out;
  }

  /** מבטיח שהכתובת הגלובלית + tip חיים גם על אנטנות space (שכפול בחלל הרשתי) */
  async ensureSpaceAddressPresence(opts = {}) {
    if (!this._spaceMesh && (SPACE_MESH.enabled || opts.force)) {
      await this.enableSpaceMesh({ force: true }).catch(() => {});
    }
    const presence = spacePresencePlan({
      canon: opts.canon || globalAddress(),
      tip: opts.tip || this.state.immortalTip?.digest || this.webGet('immortal:tip')?.digest || null,
      flockId: this.state.flock?.flockId || null,
      living: !!(this._livingAddress || LIVING_ADDRESS),
      immortal: !!(this._immortalMesh || IMMORTAL_MESH.enabled),
      probes: opts.probes || this.state.antennaProbes || [],
      spaceClass: SPACE_MESH.spaceClass,
    });
    const pin = spaceAddressPinGate(presence, opts);
    this.state.spacePresence = presence;
    saveState(this.state);
    await this.webSet('space:presence', presence).catch(() => {});
    await this.webSet('address:space', {
      canon: presence.canon, tip: presence.tip, digest: presence.digest,
      spaceAntennas: presence.spaceAntennas, connectNotControl: true, pin, ts: Date.now(),
    }).catch(() => {});
    // DTN custody של מצביע הכתובת — שורד קיטוע נתיב לווייני
    if (pin.ok || opts.force) {
      await this.dtnSend({
        kind: 'space-address',
        canon: presence.canon,
        tip: presence.tip,
        flockId: presence.flockId,
        digest: presence.digest,
      }, { forceDeliver: !!this.state.spaceDance?.alwaysOn }).catch(() => {});
    }
    return { ok: pin.ok, presence, pin };
  }

  /** שידור DTN bundle עם custody — נשמר עד שיש מסלול / פג תוקף */
  async dtnSend(payload, opts = {}) {
    const dance = this.state.spaceDance || satelliteDancePlan(RELAYS, SPACE_MESH.spaceClass, Date.now(), opts);
    const pathHint = (dance.active || []).slice(0, 4).map((a) => a.segment + ':' + a.slot);
    const bundle = dtnBundlePlan(payload, { ...opts, pathHint, src: opts.src || DEVICE, dst: opts.dst || 'mesh' });
    this.state.dtnCustody ??= {};
    this.state.dtnCustody[bundle.id] = { ...bundle, status: 'custody', ts: Date.now() };
    saveState(this.state);
    await this.webSet('dtn:bundle:' + bundle.id, this.state.dtnCustody[bundle.id]).catch(() => {});
    await this.webSet('dtn:tip', { id: bundle.id, digest: bundle.digest, ts: Date.now() }).catch(() => {});
    // ניסיון מסירה מיידית אם יש מסלול always-on
    const gate = dtnCustodyGate(bundle);
    let delivered = false;
    if (gate.deliver && (dance.alwaysOn || opts.forceDeliver)) {
      try {
        await this.pool.publish(signEvent(KIND_DATA, [['d', 'scdtn:' + keys.pubkey.slice(0, 16) + ':' + bundle.id], ['p', keys.pubkey]], JSON.stringify({
          id: bundle.id, digest: bundle.digest, custody: true, pathHint, ts: Date.now(),
        })), 8_000, 2);
        this.state.dtnCustody[bundle.id].status = 'delivered';
        this.state.dtnCustody[bundle.id].delivered = true;
        delivered = true;
        saveState(this.state);
      } catch {}
    }
    return { ok: true, bundle, delivered, custody: !delivered, dancePhase: dance.phase };
  }

  /** סריקת custody — מוחק פגים, מנסה למסור ממתינים כשהריקוד alwaysOn */
  async dtnCustodySweep(opts = {}) {
    ensureId(); deriveKeys();
    const now = Date.now();
    const bag = this.state.dtnCustody || {};
    const out = { expired: 0, delivered: 0, held: 0 };
    const dance = satelliteDancePlan(
      this.pool?.healthyUrls?.() || RELAYS,
      SPACE_MESH.spaceClass,
      now,
      opts,
    );
    for (const [id, b] of Object.entries(bag)) {
      const g = dtnCustodyGate(b, now);
      if (g.reason === 'custody-expired') {
        delete bag[id];
        out.expired++;
        continue;
      }
      if (b.delivered || b.status === 'delivered') { out.delivered++; continue; }
      if ((dance.alwaysOn || opts.forceDeliver) && this.pool) {
        try {
          await this.pool.publish(signEvent(KIND_DATA, [['d', 'scdtn:' + keys.pubkey.slice(0, 16) + ':' + id], ['p', keys.pubkey]], JSON.stringify({
            id, digest: b.digest, custody: true, pathHint: b.pathHint, resumed: true, ts: now,
          })), 8_000, 1);
          bag[id] = { ...b, status: 'delivered', delivered: true, deliveredAt: now };
          out.delivered++;
        } catch { out.held++; }
      } else out.held++;
    }
    this.state.dtnCustody = bag;
    saveState(this.state);
    await this.webSet('dtn:custody:stats', { ...out, ts: now }).catch(() => {});
    return { ok: true, ...out, alwaysOn: dance.alwaysOn, phase: dance.phase };
  }

  /** צעד ratchet צבאי — מפתח שרשרת ב-webkv (forward secrecy) */
  async militaryChannelStep(peerPub, opts = {}) {
    ensureId(); deriveKeys();
    const prev = this.webGet('mil:ratchet:' + String(peerPub || 'mesh').slice(0, 24)) || { chainKey: hex(sha256(Buffer.from('sc-mil-root:' + syncId))), epoch: 0 };
    let dh;
    try { dh = ecdhKey(keys.priv, peerPub || keys.pubkey); }
    catch { dh = sha256(Buffer.from('sc-mil-dh:' + (peerPub || 'mesh') + ':' + prev.epoch)); }
    const step = militaryRatchetStep(prev.chainKey, dh, { epoch: prev.epoch, info: opts.info });
    const body = { ...step, peer: peerPub || 'mesh', ts: Date.now() };
    await this.webSet('mil:ratchet:' + String(peerPub || 'mesh').slice(0, 24), body).catch(() => {});
    return body;
  }

  async spaceMeshReport() {
    const dance = this.state.spaceDance || satelliteDancePlan(RELAYS, SPACE_MESH.spaceClass, Date.now());
    const plan = buildOrbitalContactPlan({ now: Date.now() });
    const route = contactGraphRoute(plan, 'ground', 'orb:' + (dance.visibleSlots?.[0] ?? 0));
    return spaceMeshManifest(dance, route, {});
  }

  /**
   * Immortal-Ι — שכפול עצמי על אנטנות הרשת.
   * מזהה נתיבים (כולל לווייניים לפי RTT), משכפל tip לכל האנטנות, חי גם בלי מקור נגיש.
   */
  async enableImmortalMesh(opts = {}) {
    if (!IMMORTAL_MESH.enabled && !opts.force) return { ok: false, reason: 'immortal-disabled' };
    this._immortalMesh = true;
    const antennas = antennaInventoryPlan({
      relays: RELAYS.concat(RELAYS_FALLBACK).slice(0, 16),
      blossom: BLOSSOM_SERVERS.slice(0, 8),
      nsite: mirrorAddresses().slice(0, 8),
      space: SPACE_MESH.spaceClass || [],
    });
    const probes = await this.probeNetworkAntennas({ limit: opts.probeLimit || 8, antennas: antennas.antennas }).catch(() => []);
    const pathClasses = probes.map((p) => ({ url: p.url, kind: p.kind, ...networkPathClassFromProbe(p) }));
    const tip = immortalReplicaTip({
      canon: globalAddress(),
      capsule: this.state.capsuleMeshTip?.digest || this.state.capsuleCanaryTip?.digest || null,
      living: !!(this._livingAddress || this.state.livingAddress || LIVING_ADDRESS),
      fullAppOk: this.state.fullAppGate?.ok !== false,
      dialChannel: this.state.meshDial?.channel || null,
      spacePhase: this.state.spaceDance?.phase ?? null,
    });
    const plan = selfReplicationPlan({ tip, antennas, pathClasses });
    this.state.immortal = { tip, antennas: antennas.n, pathClasses, plan, ts: Date.now() };
    saveState(this.state);
    await this.webSet('immortal:plan', plan).catch(() => {});
    await this.webSet('immortal:tip', tip).catch(() => {});
    await this.webSet('immortal:paths', { pathClasses, ts: Date.now() }).catch(() => {});
    const rep = await this.replicateSelf({ tip, probes, skipPack: opts.skipPack }).catch((e) => ({ ok: false, error: e.message }));
    log(`Immortal: tip=${tip.digest.slice(0, 12)}… antennas=${antennas.n} satPaths=${pathClasses.filter((p) => p.satelliteLikely).length} · originOptional=true`);
    return { ok: true, tip, antennas, pathClasses, plan, replicate: rep };
  }

  /** Probe אנטנות — מדידת RTT לסיווג terrestrial / satellite-path */
  async probeNetworkAntennas(opts = {}) {
    const list = (opts.antennas || antennaInventoryPlan({
      relays: RELAYS.slice(0, 6), blossom: BLOSSOM_SERVERS.slice(0, 4), nsite: mirrorAddresses().slice(0, 4),
    }).antennas).slice(0, opts.limit || 10);
    const out = [];
    await Promise.all(list.map(async (a) => {
      const t0 = Date.now();
      let ok = false, status = 0;
      try {
        const ctrl = new AbortController();
        const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs || IMMORTAL_MESH.probeTimeoutMs || 4000);
        let url = a.url;
        if (a.kind === 'relay') {
          // WS probes via TCP-ish: try https upgrade host HEAD
          url = a.url.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:');
        } else if (a.kind === 'nsite') {
          url = String(a.url).replace(/\/$/, '') + '/skylive.json';
        }
        const res = await fetch(url, { method: 'GET', signal: ctrl.signal, cache: 'no-store' }).catch(() => null);
        clearTimeout(timer);
        ok = !!(res && (res.ok || res.status < 500));
        status = res?.status || 0;
      } catch {}
      const rttMs = Date.now() - t0;
      out.push({ ...a, ok, status, rttMs, jitterMs: Math.max(0, rttMs - 80), ts: Date.now() });
    }));
    this.state.antennaProbes = out;
    saveState(this.state);
    await this.webSet('immortal:probes', { probes: out, ts: Date.now() }).catch(() => {});
    return out;
  }

  /** שכפול עצמי — מפרסם מחדש לכל האנטנות ורושם replica tip (+ ADO brain member) */
  async replicateSelf(opts = {}) {
    ensureId(); deriveKeys();
    const brainDigest = brainDigestFromSelfModel(this._self || this.state.selfModel || {}, { genome: this.state.genome });
    const merkle = opts.merkle || this.state.fullAppMerkle || null;
    const continuum = cryptoAppContinuumManifest({
      capsule: this.state.capsuleMeshTip?.digest || null,
      nsite: globalAddress(),
      dial: this.state.meshDial?.channel || null,
      merkleRoot: merkle?.merkleRoot || merkle?.root || null,
      tipDigest: null,
      living: !!(this._livingAddress || LIVING_ADDRESS),
    });
    let tip = opts.tip || immortalReplicaTip({
      canon: globalAddress(),
      capsule: this.state.capsuleMeshTip?.digest || null,
      living: !!(this._livingAddress || LIVING_ADDRESS),
      fullAppOk: this.state.fullAppGate?.ok !== false,
      dialChannel: this.state.meshDial?.channel || null,
      spacePhase: this.state.spaceDance?.phase ?? null,
      merkleRoot: merkle?.merkleRoot || merkle?.root || null,
      continuumDigest: continuum.digest,
      brainDigest,
    });
    // S136: living publish נחסם בלי continuum מלא (אלא אם skip)
    const contGate = cryptoAppContinuumGate(continuum, { allowIncomplete: opts.allowIncompleteContinuum || !merkle });
    if (!contGate.ok && (this._livingAddress || LIVING_ADDRESS) && opts.requireContinuum) {
      return { ok: false, reason: contGate.reason, continuum };
    }
    // פרסום מלא לרשת — tip nsite + blobs + living
    this.forceVerifyBlobs = true;
    if (PUBLIC_WEBSITE) await this._publishWebsite().catch((err) => log('Immortal publish:', err.message));
    await this._publishSite().catch(() => {});
    if (this._livingAddress || LIVING_ADDRESS) {
      await this.publishLivingSnapshots({}).catch(() => {});
    }
    await this.ensureZeroOriginContinuity().catch(() => {});
    if (!opts.skipPack && (SITE_DIR || opts.dir)) {
      try {
        const packed = await this.packCapsule(opts.dir || SITE_DIR || 'site', { keepId: true });
        if (packed?.digest || packed?.out) {
          tip = immortalReplicaTip({ ...tip, capsule: packed.digest || tip.capsule, merkleRoot: tip.merkle, continuumDigest: continuum.digest, brainDigest });
        }
      } catch {}
    }
    // רישום שכפול מקומי + מיזוג registry (כל עותק = מוח + חבר)
    const pathClass = (opts.probes || this.state.antennaProbes || []).map((p) => networkPathClassFromProbe(p).class).find((c) => c === 'satellite-path') || 'terrestrial';
    const member = organismMemberManifest({
      device: DEVICE, tipDigest: tip.digest, brainDigest, genome: this.state.genome || null,
      capabilities: ['mesh', 'brain', 'node'], pathClass,
      node: !!(this._dbApiEnabled || this._fullNodeServer),
    });
    const self = {
      id: DEVICE, digest: tip.digest, canon: tip.canon, living: tip.living,
      node: true, brain: true, brainDigest, memberDigest: member.digest,
      pathClass, ts: Date.now(),
    };
    const remote = this.webGet('immortal:replicas') || {};
    const merged = { ...remote, [DEVICE]: self };
    const members = { ...(this.webGet('ado:members') || {}), [DEVICE]: member };
    const now = Date.now();
    for (const [id, r] of Object.entries(merged)) {
      if (!r?.ts || now - r.ts > 600_000) delete merged[id];
    }
    for (const [id, m] of Object.entries(members)) {
      if (!m?.ts || now - m.ts > 600_000) delete members[id];
    }
    this.state.immortalReplicas = merged;
    this.state.immortalTip = tip;
    this.state.adoMembers = members;
    this.state.appContinuum = continuum;
    saveState(this.state);
    await this.webSet('immortal:replicas', merged).catch(() => {});
    await this.webSet('immortal:tip', tip).catch(() => {});
    await this.webSet('ado:members', members).catch(() => {});
    await this.webSet('ado:member:' + DEVICE, member).catch(() => {});
    await this.webSet('app:continuum', continuum).catch(() => {});
    await this.publishName('immortal-tip', 'imm:' + tip.digest).catch(() => {});
    await this.publishName('ado-member', 'ado:' + member.digest).catch(() => {});
    await this.beatWorker({ immortal: true, immortalDigest: tip.digest, brain: true, brainDigest }).catch(() => {});
    await this.publishCdnLattice().catch(() => {});
    const ants = (opts.probes || this.state.antennaProbes || []).map((p) => ({ ...p, ...networkPathClassFromProbe(p) }));
    const extinction = antiExtinctionGate(merged, ants, {});
    const orgQ = organismQuorumGate(members, ants, {});
    await this.webSet('immortal:extinction', extinction).catch(() => {});
    await this.webSet('ado:quorum', orgQ).catch(() => {});
    if (extinction.extinct || orgQ.extinct) {
      log('Immortal/ADO: אזהרת הכחדה —', extinction.reason || orgQ.reason);
    }
    // כתובת+שכפולים חיים גם על נתיבי space (connect ≠ control)
    const spacePin = await this.ensureSpaceAddressPresence({
      tip: tip.digest, probes: ants, force: true,
    }).catch((e) => ({ ok: false, error: e.message }));
    return {
      ok: true, tip, self, member, continuum,
      replicas: Object.keys(merged).length, extinction, organism: orgQ,
      spacePresence: spacePin?.presence || this.state.spacePresence || null,
      spacePin,
    };
  }

  /** סריקת אלמוות — עדכון שכפולים + מניעת הכחדה + ADO quorum/partition */
  async immortalSweep(opts = {}) {
    if (!this._immortalMesh && !IMMORTAL_MESH.enabled && !opts.force) {
      return { ok: false, reason: 'immortal-off' };
    }
    const probes = await this.probeNetworkAntennas({ limit: opts.probeLimit || 8 }).catch(() => this.state.antennaProbes || []);
    const ants = probes.map((p) => ({ ...p, ...networkPathClassFromProbe(p), pathClass: networkPathClassFromProbe(p).class }));
    const remote = this.webGet('immortal:replicas') || this.state.immortalReplicas || {};
    const members = this.webGet('ado:members') || this.state.adoMembers || {};
    const tipRemote = this.webGet('immortal:tip') || this.state.immortalTip || null;
    const brainDigest = brainDigestFromSelfModel(this._self || this.state.selfModel || {}, { genome: this.state.genome });
    const localTip = immortalReplicaTip({
      canon: globalAddress(),
      capsule: this.state.capsuleMeshTip?.digest || tipRemote?.capsule || null,
      living: !!(this._livingAddress || LIVING_ADDRESS),
      fullAppOk: this.state.fullAppGate?.ok !== false,
      dialChannel: this.state.meshDial?.channel || tipRemote?.dial || null,
      spacePhase: this.state.spaceDance?.phase ?? tipRemote?.spacePhase ?? null,
      merkleRoot: this.state.fullAppMerkle?.merkleRoot || tipRemote?.merkle || null,
      continuumDigest: this.state.appContinuum?.digest || tipRemote?.continuum || null,
      brainDigest,
    });
    const upd = replicaUpdatePlan(localTip, remote, {
      minReplicas: IMMORTAL_MESH.minReplicas,
      remoteTip: tipRemote?.digest || null,
    });
    let replicated = null;
    const extinction = antiExtinctionGate(remote, ants, {});
    const orgQ = organismQuorumGate(members, ants, {});
    const part = partitionHealPlan(remote, {});
    const hist = this.state.extinctionHist || [];
    hist.push({ nReplicas: extinction.nReplicas, nAntennas: extinction.nAntennas, ts: Date.now() });
    if (hist.length > 32) hist.splice(0, hist.length - 32);
    this.state.extinctionHist = hist;
    const hazard = extinctionHazardScore(hist, {});
    if (extinction.extinct || orgQ.extinct || upd.needPush || hazard.preempt || opts.force) {
      replicated = await this.replicateSelf({ tip: localTip, probes, skipPack: opts.skipPack !== false });
    }
    if (upd.needPull && tipRemote?.digest) {
      // partition tip election אם יש מחלוקת
      if (part.tip && part.tip !== localTip.digest) {
        this.state.immortalTip = { ...(tipRemote || {}), digest: part.tip, elected: true };
      } else {
        this.state.immortalTip = tipRemote;
      }
      saveState(this.state);
      await this.pullCapsuleFromMesh({}).catch(() => {});
    }
    // Flock mind merge — להקה אחת
    const flockId = this.state.flock?.flockId || ('flock:' + (this.state.skyHandle || SITE_SLUG || 'local'));
    const flockMembers = Object.fromEntries(Object.entries(members).map(([id, m]) => [id, {
      ...m,
      flockId,
      tipDigest: localTip.digest,
      brainDigest: m.brainDigest || brainDigest,
      mindDigest: m.mindDigest || flockMindDigest({ flockId, tipDigest: localTip.digest, brainDigests: [m.brainDigest || brainDigest] }),
    }]));
    const flock = flockMergePlan(flockMembers, { flockId, tipDigest: localTip.digest });
    const roost = this.state.addressRoam || this.webGet('address:roam');
    const votesRoost = {};
    for (const r of (roost?.roosts || [])) {
      votesRoost[r.satId] = [
        { peerId: 'self', satId: r.satId },
        { peerId: 'mesh', satId: r.satId },
      ];
    }
    const flockRoostMembers = Object.fromEntries(Object.entries(flockMembers).map(([id, m], i) => {
      const sat = m.satId || roost?.roosts?.[i % Math.max(1, roost?.roosts?.length || 1)]?.satId;
      return [id, { ...m, satId: sat }];
    }));
    const flockOnRoosts = flockMergeOnRoostsPlan(flockRoostMembers, roost, votesRoost, {});
    const reach = flockAntennaReachPlan(ants, {});
    const satGate = satellitePathConnectGate(
      ants.find((a) => a.satelliteLikely) || { ok: true, rttMs: 40 },
      ants,
      {},
    );
    this.state.flock = { flockId, ...flock, roostMerge: flockOnRoosts, reach, satGate, ts: Date.now() };
    await this.webSet('ado:flock', this.state.flock).catch(() => {});
    // S142: roam digest changed → mark skylive dirty / republish path
    const roamPub = roamSkyliveAutopublishGate(this.state.addressRoamPrev, roost, {});
    if (roamPub.republish) {
      this.state.skyliveRoamDirty = true;
      this.state.addressRoamPrev = roost ? { roamDigest: roost.roamDigest } : this.state.addressRoamPrev;
      await this.webSet('skylive:roamDirty', { digest: roamPub.digest, ts: Date.now() }).catch(() => {});
    }
    // Hop-vault extinction hazard → preempt replicate
    const vaultHazard = hopVaultExtinctionHazardPlan(this.state.hopVault, hist, {});
    if (vaultHazard.preempt && !replicated) {
      replicated = await this.replicateSelf({ tip: localTip, probes, skipPack: opts.skipPack !== false }).catch(() => null);
    }
    // S134: degraded → full כש-mesh/organism חוזר
    const promote = degradedFullPromotePlan({
      workersAlive: slaScoreFromWorkers(this.state.workers || {}).workersAlive,
      meshAlive: Object.keys(this.state.workers || {}).length > 0,
      organismQuorum: orgQ,
    });
    if (promote.promote) {
      await this.webSet('app:ui', { profile: 'full', ts: Date.now(), reason: promote.reason }).catch(() => {});
      await this.publishName('app:ui', 'profile:full').catch(() => {});
    }
    const roamBadge = roamHealthBadgePlan(roost, slaScoreFromWorkers(this.state.workers || {}), {});
    await this.webSet('immortal:sweep', {
      upd, extinction, organism: orgQ, partition: part, hazard, promote, flock, flockOnRoosts,
      satGate, roamPub, vaultHazard, roamBadge, ts: Date.now(),
    }).catch(() => {});
    return {
      ok: true, upd, extinction, organism: orgQ, partition: part, hazard, promote, flock, flockOnRoosts,
      reach, satGate, replicated, roamPub, vaultHazard, roamBadge,
      satPaths: ants.filter((a) => a.satelliteLikely).length,
      note: 'ADO flock alive on antennas; one mind; origin optional',
    };
  }

  /**
   * ADO — Autonomous Distributed Organism
   * כל עותק = מוח (SelfModel) + חבר mesh; אין נקודת כיבוי יחידה.
   */
  async enableADO(opts = {}) {
    if (!ADO.enabled && !opts.force) return { ok: false, reason: 'ado-disabled' };
    if (this._ado && opts.force !== true && this.state.ado) return this.state.ado;
    this._ado = true;
    if (!this._immortalMesh) await this.enableImmortalMesh({ force: true, skipPack: opts.skipPack !== false }).catch(() => {});
    if ((this._livingAddress || LIVING_ADDRESS) && !this._livingAddressReady) {
      await this.enableLivingAddress({ force: true, immortal: false, ado: false }).catch(() => {});
    }
    const brainDigest = brainDigestFromSelfModel(this._self || this.state.selfModel || {}, { genome: this.state.genome });
    const tip = this.state.immortalTip || this.webGet('immortal:tip');
    const member = organismMemberManifest({
      device: DEVICE, tipDigest: tip?.digest, brainDigest, genome: this.state.genome,
      capabilities: ['mesh', 'brain', 'vault', 'submind'], pathClass: 'terrestrial', node: true,
    });
    const members = { ...(this.webGet('ado:members') || {}), [DEVICE]: member };
    this.state.adoMembers = members;
    const ants = (this.state.antennaProbes || []).map((p) => ({ ...p, ...networkPathClassFromProbe(p) }));
    const quorum = organismQuorumGate(members, ants.length ? ants : [{ ok: true }, { ok: true }, { ok: true }], {});
    const census = organismCensusPlan(members, ants, {});
    const passport = sovereignPassportPlan({
      callingCard: this.state.callingCard || this.webGet('calling-card')?.url || null,
      genome: this.state.genome, tipDigest: tip?.digest, memberDigest: member.digest,
      handle: this.state.skyHandle || SITE_SLUG,
    });
    const law = latticeLawGate(passport, this.state.trustRotate || {}, { requireCard: false });
    const flockId = ADIS.flockId || ('flock:' + (this.state.skyHandle || SITE_SLUG || keys?.pubkey?.slice(0, 12) || 'local'));
    const flock = flockMergePlan(
      Object.fromEntries(Object.entries(members).map(([id, m]) => [id, {
        ...m, flockId, mindDigest: m.mindDigest || flockMindDigest({ flockId, tipDigest: tip?.digest, brainDigests: [brainDigest] }),
        brainDigest: m.brainDigest || brainDigest, tipDigest: tip?.digest,
      }])),
      { flockId, tipDigest: tip?.digest, kernelDigest: this.state.adisKernel?.digest },
    );
    const reach = flockAntennaReachPlan(ants, {});
    const satGate = satellitePathConnectGate(
      ants.find((a) => a.satelliteLikely) || ants[0] || { ok: true, rttMs: 40 },
      ants,
      {},
    );
    this.state.ado = { member, quorum, census, passport, law, flock, reach, satGate, flockId, ts: Date.now() };
    this.state.flock = { flockId, mindDigest: flock.mindDigest, ...flock };
    saveState(this.state);
    await this.webSet('ado:members', members).catch(() => {});
    await this.webSet('ado:census', census).catch(() => {});
    await this.webSet('ado:passport', passport).catch(() => {});
    await this.webSet('ado:flock', this.state.flock).catch(() => {});
    await this.replicateSelf({ skipPack: true, allowIncompleteContinuum: true }).catch(() => {});
    await this.beatWorker({ ado: true, brain: true, brainDigest, flockId, mindDigest: flock.mindDigest }).catch(() => {});
    this._adisPush?.({ type: 'flock', msg: `merge ${flock.mindDigest.slice(0, 8)} brains=${quorum.nBrains}`, flockId });
    log(`ADO: member=${member.digest.slice(0, 12)}… flock=${flock.mindDigest.slice(0, 12)}… satPaths=${reach.nSatellitePaths} · unkillable mesh`);
    return { ok: true, member, quorum, census, passport, law, flock, reach, satGate };
  }

  /** SIOS — Sovereign Internet OS micro-kernel */
  async enableSIOS(opts = {}) {
    if (!SIOS.enabled && !opts.force) return { ok: false, reason: 'sios-disabled' };
    this._sios = true;
    const manifest = siosPluginManifest(opts);
    this.state.sios = { manifest, plugins: { ...manifest.plugins }, ts: Date.now() };
    for (const [name, on] of Object.entries(manifest.plugins)) {
      if (on) await this.enablePlugin(name, opts).catch(() => {});
    }
    saveState(this.state);
    await this.webSet('sios:manifest', manifest).catch(() => {});
    log('SIOS: micro-kernel · plugins=' + Object.keys(manifest.plugins).filter((k) => manifest.plugins[k]).join(','));
    return { ok: true, manifest };
  }

  async enablePlugin(name, opts = {}) {
    this.state.siosPlugins ??= {};
    if (name === 'economy') {
      const meter = resourceMeterSample(opts.sample || { cpu: 0.15, ram: 0.25, hostSec: 1 }, {});
      const credit = resourceCreditPlan(meter, {});
      this.state.siosPlugins.economy = { meter, credit, ts: Date.now() };
      await this.webSet('sios:economy', this.state.siosPlugins.economy).catch(() => {});
      return this.state.siosPlugins.economy;
    }
    if (name === 'runtime') {
      this.state.siosPlugins.runtime = { sandbox: true, wasm: true, nabla: true, ts: Date.now() };
      await this.webSet('sios:runtime', this.state.siosPlugins.runtime).catch(() => {});
      return this.state.siosPlugins.runtime;
    }
    if (name === 'skydrive') {
      this.state.siosPlugins.skydrive = { root: '/', engine: 'vault-rs-blossom', ts: Date.now() };
      await this.webSet('sios:skydrive', this.state.siosPlugins.skydrive).catch(() => {});
      return this.state.siosPlugins.skydrive;
    }
    if (name === 'did') {
      ensureId(); deriveKeys();
      const doc = didDocumentPlan({
        handle: this.state.skyHandle || SITE_SLUG, pubkey: keys.pubkey,
        callingCard: this.state.callingCard || null,
        passportDigest: this.state.ado?.passport?.digest, flockId: this.state.flock?.flockId,
      });
      this.state.didDoc = doc;
      this.state.siosPlugins.did = { id: doc.id, digest: doc.digest, ts: Date.now() };
      await this.webSet('sios:did', doc).catch(() => {});
      await this.webSet('did:' + doc.id, doc).catch(() => {});
      return doc;
    }
    if (name === 'gateway') {
      this.state.siosPlugins.gateway = { modes: ['sky-uri', 'well-known', 'omega-api'], ts: Date.now() };
      await this.webSet('sios:gateway', this.state.siosPlugins.gateway).catch(() => {});
      return this.state.siosPlugins.gateway;
    }
    return { ok: false, reason: 'unknown-plugin', name };
  }

  async skyDriveHeal(opts = {}) {
    const meta = this.state.skyDrive || this.webGet('sky:drive') || {};
    const shares = opts.shares || [];
    return skyDriveHealPlan(meta, shares, opts);
  }

  async runtimeInvoke(bytesOrB64, opts = {}) {
    if (!process.env.SC_TEST && process.env.SC_API_TOKEN && !opts.authorized) {
      return { ok: false, reason: 'runtime-invoke-denied', hint: 'set authorized:true with SC_API_TOKEN' };
    }
    if (opts.imports && Object.keys(opts.imports).length > (opts.maxImports || 8)) {
      return { ok: false, reason: 'imports-limited' };
    }
    const plan = wasmModulePlan(bytesOrB64, opts);
    if (!plan.ok) return plan;
    const inv = runtimeInvokePlan(plan, opts.args || [], opts);
    try {
      const buf = Buffer.isBuffer(bytesOrB64) ? bytesOrB64 : Buffer.from(String(bytesOrB64), opts.encoding || 'base64');
      const mod = await WebAssembly.compile(buf);
      const inst = await WebAssembly.instantiate(mod, opts.imports || {});
      const fn = inst.exports[opts.export || 'main'] || inst.exports.main || Object.values(inst.exports).find((x) => typeof x === 'function');
      const result = typeof fn === 'function' ? fn(...(opts.args || [])) : null;
      this._adisPush?.({ type: 'wasm', msg: `invoke ${plan.digest.slice(0, 8)}=${result}` });
      return { ok: true, result, plan, inv };
    } catch (err) {
      return { ok: false, reason: err.message, plan, inv };
    }
  }

  async skyDrivePut(pathStr, data, opts = {}) {
    const plan = skyDrivePutPlan(pathStr, data, opts);
    if (!plan.ok) return plan;
    const uploaded = [];
    for (let i = 0; i < (plan.sharesB64 || []).length; i++) {
      try {
        const { hash } = await blossomUpload(Buffer.from(plan.sharesB64[i], 'base64'), 'application/octet-stream');
        uploaded.push({ i, hash });
      } catch { uploaded.push({ i, local: true }); }
    }
    const meta = { path: plan.path, digest: plan.digest, merkleRoot: plan.merkleRoot, leaf: plan.leaf, plan: { k: plan.plan.k, n: plan.plan.n, chunk: plan.plan.chunk, size: plan.size, digests: plan.plan.digests, blobDigest: plan.plan.blobDigest }, shares: uploaded, sharesB64: plan.sharesB64, ts: Date.now() };
    this.state.skyDrive ??= {};
    this.state.skyDrive[plan.path] = meta;
    saveState(this.state);
    await this.webSet('skydrive:' + plan.path, { ...meta, sharesB64: undefined }).catch(() => {});
    return { ok: true, ...meta, nShares: uploaded.length };
  }

  async skyDriveGet(pathStr, opts = {}) {
    const meta = this.state.skyDrive?.[pathStr] || this.webGet('skydrive:' + pathStr);
    if (!meta) return { ok: false, reason: 'not-found', path: pathStr };
    return skyDriveGetPlan(meta, meta.sharesB64 || opts.shares, opts);
  }

  // ─────────── Swarm Mesh S173–S180 — multi-agent on SYNC_ID ───────────

  swarmRegistry() {
    const map = new Map();
    for (const a of Object.values(this.state.swarmAgents || {})) {
      if (a?.agentId) map.set(a.agentId, a);
    }
    try {
      for (const [k, v] of Object.entries(this.webGetAll())) {
        if (k.startsWith('swarm:agent:') && v?.agentId) map.set(v.agentId, v);
      }
    } catch {}
    return [...map.values()];
  }

  async swarmRegister(parts = {}) {
    if (!SWARM_MESH.enabled && !parts.force) return { ok: false, reason: 'swarm-disabled' };
    ensureId(); deriveKeys();
    const manifest = swarmNodeManifestPlan({
      ...parts,
      syncId,
      deviceId: DEVICE,
      pubkey: keys.pubkey,
      ts: Date.now(),
    });
    const gate = swarmRegisterGate(manifest, this.swarmRegistry(), parts);
    if (!gate.ok) return gate;
    this.state.swarmAgents ??= {};
    this.state.swarmAgents[manifest.agentId] = manifest;
    saveState(this.state);
    await this.webSet('swarm:agent:' + manifest.agentId, manifest).catch(() => {});
    await this.webSet('agent:registry', { agents: this.swarmRegistry(), ts: Date.now() }).catch(() => {});
    return { ok: true, manifest, registryKey: gate.registryKey };
  }

  swarmDiscover(query = {}, opts = {}) {
    return swarmDiscoverPlan(this.swarmRegistry(), query, opts);
  }

  async swarmHandoff(sessionId, toAgentId, opts = {}) {
    const sid = String(sessionId || '');
    const session = this.state.swarmSessions?.[sid] || this.webGet('swarm:session:' + sid);
    if (!session) return { ok: false, reason: 'no-session', sessionId: sid };
    const toAgent = this.swarmRegistry().find((a) => a.agentId === toAgentId) || { agentId: toAgentId };
    const plan = swarmHandoffPlan(session, opts.fromAgent || opts.from, toAgent, opts);
    if (!plan.ok) return plan;
    this.state.swarmHandoffs ??= {};
    this.state.swarmHandoffs[plan.handoff.digest] = plan.handoff;
    await this.webSet(plan.key, plan.handoff).catch(() => {});
    session.status = 'handoff';
    session.lastHandoff = plan.handoff.digest;
    this.state.swarmSessions ??= {};
    this.state.swarmSessions[sid] = session;
    await this.webSet('swarm:session:' + sid, session).catch(() => {});
    return plan;
  }

  swarmSynthesize(answers = [], opts = {}) {
    return swarmSynthesizePlan(answers, opts);
  }

  async swarmAsk(input, opts = {}) {
    const prompt = typeof input === 'string' ? input : (input?.content || xiCanon(input));
    const sessionId = opts.sessionId || hex(sha256(Buffer.from('swarm:session:' + prompt + ':' + Date.now()))).slice(0, 24);
    const discovered = this.swarmDiscover({ capability: opts.capability, role: opts.role, query: prompt }, opts);
    const agent = discovered.nodes?.[0];
    if (!agent) return { ok: false, reason: 'no-agent', sessionId };
    const session = {
      sessionId,
      history: [{ role: 'user', content: prompt, ts: Date.now() }],
      steps: [],
      ts: Date.now(),
      routedTo: agent.agentId,
    };
    const digest = swarmSessionDigestPlan(sessionId, session.steps);
    session.digest = digest.digest;
    this.state.swarmSessions ??= {};
    this.state.swarmSessions[sessionId] = session;
    let content = null;
    if (this._adis) {
      const r = await this.adisAsk(prompt, opts).catch(() => null);
      content = r?.content ?? r?.answer ?? null;
    }
    if (content == null) content = 'swarm:' + agent.role + ':' + prompt.slice(0, 240);
    session.steps.push({
      agentId: agent.agentId, op: 'ask',
      digest: hex(sha256(Buffer.from(String(content)))).slice(0, 16),
      ts: Date.now(),
    });
    session.history.push({ role: 'assistant', content, agentId: agent.agentId, ts: Date.now() });
    session.digest = swarmSessionDigestPlan(sessionId, session.steps).digest;
    await this.webSet('swarm:session:' + sessionId, session).catch(() => {});
    saveState(this.state);
    return { ok: true, sessionId, agentId: agent.agentId, content, nodes: discovered.nodes?.length || 0 };
  }

  toolLibrarySearch(query, opts = {}) {
    const tools = Object.values(this.state.swarmTools || {}).concat(
      Object.values(this.state.adisTools || {}),
    );
    try {
      for (const [k, v] of Object.entries(this.webGetAll())) {
        if (k.startsWith('swarm:tool:') && v?.id) tools.push(v);
      }
    } catch {}
    return toolLibrarySearchPlan(query, tools, opts);
  }

  async toolLibraryPublish(tool, opts = {}) {
    const pub = dtaPublishSwarmPlan(tool, opts);
    if (!pub.ok) return pub;
    this.state.swarmTools ??= {};
    this.state.swarmTools[tool.id] = { ...tool, version: pub.version, ts: Date.now() };
    saveState(this.state);
    await this.webSet(pub.swarmKey, { sig: pub.sig, version: pub.version, tool, deprecateAfter: pub.deprecateAfter }).catch(() => {});
    if (pub.key) await this.webSet(pub.key, tool).catch(() => {});
    return pub;
  }

  toolLibraryLazyInject(tools, opts = {}) {
    return toolLibraryLazyInjectPlan(tools, opts);
  }

  toolLibraryDeprecate(opts = {}) {
    const tools = Object.values(this.state.swarmTools || {});
    const plan = toolLibraryDeprecatePlan(tools, opts);
    this.state.swarmTools = Object.fromEntries(plan.kept.map((t) => [t.id, t]));
    saveState(this.state);
    return plan;
  }

  async swarmMeshTick() {
    if (!SWARM_MESH.enabled) return { ok: false, reason: 'swarm-disabled' };
    const now = Date.now();
    const registry = this.swarmRegistry().filter((a) => now - (a.ts || 0) < (a.ttlMs ?? SWARM_MESH.heartbeatMs * 3));
    this.state.swarmAgents = Object.fromEntries(registry.map((a) => [a.agentId, a]));
    const handoffs = [];
    const sessions = [];
    try {
      for (const [k, v] of Object.entries(this.webGetAll())) {
        if (k.startsWith('swarm:handoff:') && v?.digest) handoffs.push(v);
        if (k.startsWith('swarm:session:') && v?.sessionId) sessions.push(v);
        if (k.startsWith('swarm:tool:') && v?.tool?.id) {
          this.state.swarmTools ??= {};
          this.state.swarmTools[v.tool.id] = v.tool;
        }
      }
    } catch {}
    this.state.swarmSessions = Object.fromEntries(sessions.map((s) => [s.sessionId, s]));
    saveState(this.state);
    await this.webSet('swarm:mesh:tip', {
      nAgents: registry.length, nHandoffs: handoffs.length, nSessions: sessions.length, ts: now,
    }).catch(() => {});
    await this.webSet('agent:registry', { agents: registry, ts: now }).catch(() => {});
    return { ok: true, agents: registry.length, handoffs: handoffs.length, sessions: sessions.length };
  }

  /** ADIS — enable cognitive swarm */
  async enableADIS(opts = {}) {
    if (!ADIS.enabled && !opts.force) return { ok: false, reason: 'adis-disabled' };
    this._adis = true;
    this.state.adisEvents = this.state.adisEvents || [];
    this._adisPush = (ev) => {
      const t = adisTracePlan(ev, { flockId: this.state.flock?.flockId });
      this.state.adisEvents.push(t);
      if (this.state.adisEvents.length > 200) this.state.adisEvents.splice(0, this.state.adisEvents.length - 200);
      saveState(this.state);
      this.webSet('adis:events:tip', { n: this.state.adisEvents.length, last: t, ts: Date.now() }).catch(() => {});
    };
    if (!this._sios) await this.enableSIOS({ force: true }).catch(() => {});
    if (!this._ado) await this.enableADO({ force: true, skipPack: true }).catch(() => {});
    const kernel = embedLattice('adis-kernel:' + (this.state.flock?.flockId || 'local'));
    this.state.adisKernel = kernel;
    this.state.adisVec = this.state.adisVec || {};
    // seed memory
    const seed = vectorLatticePutPlan('kernel:boot', 'ADIS cognitive kernel online flock=' + (this.state.flock?.mindDigest || ''), {});
    this.state.adisVec[seed.key] = seed;
    if (opts.slmAuto || ADIS.slmAuto) {
      await this.slmBootstrap(opts.manifest || null).catch((e) => this._adisPush({ type: 'slm', msg: 'bootstrap-skip ' + e.message }));
    }
    this._adisPush({ type: 'boot', msg: 'ADIS online kernel=' + kernel.digest.slice(0, 8), flockId: this.state.flock?.flockId });
    saveState(this.state);
    await this.webSet('adis:status', { ok: true, kernel: kernel.digest, flock: this.state.flock, ts: Date.now() }).catch(() => {});
    log('ADIS: Organic AI Swarm · kernel=' + kernel.digest.slice(0, 12) + ' · flock mind fused');
    return { ok: true, kernel, flock: this.state.flock };
  }

  async adisRemember(key, text, opts = {}) {
    const put = vectorLatticePutPlan(key, text, opts);
    this.state.adisVec ??= {};
    this.state.adisVec[put.key] = put;
    saveState(this.state);
    if (this._skydb) {
      try { await this.db.put('adis:vec', put.key, put); } catch {}
    }
    await this.webSet('adis:vec:' + put.key, { digest: put.emb.digest, meta: put.meta, ts: put.ts }).catch(() => {});
    this._adisPush?.({ type: 'vec', msg: 'put ' + put.key });
    return put;
  }

  async adisAsk(input, opts = {}) {
    const prompt = typeof input === 'string' ? input : (input?.content || xiCanon(input));
    const store = this.state.adisVec || {};
    const recall = adisRecallPlan(prompt, store, opts);
    let toolOut = null;
    if (!recall.recall.top || (recall.recall.top.score || 0) < (opts.minScore ?? 0.25)) {
      const tool = dtaToolPlan(prompt, { kind: 'formula', body: 'x' });
      const pub = dtaPublishPlan(tool, {});
      this.state.adisTools ??= {};
      this.state.adisTools[tool.id] = tool;
      await this.webSet(pub.key, tool).catch(() => {});
      toolOut = dtaInvokePlan({ ...tool, kind: 'formula', body: 'x*1+1' }, recall.recall.top?.score || 0);
      this._adisPush?.({ type: 'dta', msg: 'tool ' + tool.id });
    }
    const kernelContent = recall.recall.top
      ? `[kernel-recall score=${recall.recall.top.score}] ${prompt}`
      : `[kernel-cold] ${prompt}`;
    const sources = [
      { lobe: 'kernel', content: kernelContent, emb: embedLattice(kernelContent), weight: 1.2 },
    ];
    if (toolOut?.ok) sources.push({ lobe: 'dta', content: String(toolOut.output), weight: 0.5 });
    // SLM lobe
    if (this.state.slm?.ok || opts.fixture) {
      const slm = slmInferPlan(prompt, {
        preferMesh: ADIS.preferMeshInfer, workersAlive: slaScoreFromWorkers(this.state.workers || {}).workersAlive,
        fixture: true, allowLocal: true,
      });
      if (slm.ok) sources.push({ lobe: 'slm', content: slm.content, emb: slm.emb, weight: 1 });
    }
    // API lobes (fused, not separate agents)
    const apis = (this._mindCfg?.apis || opts.apis || []).slice(0, ADIS.maxApiLobes || 32);
    if ((MIND.enabled || this._mindCfg?.enabled || opts.useApi) && (apis.length || this._mindApiKey?.(this._mindCfg || MIND))) {
      try {
        const apiAns = await this.askMind(prompt, { ...opts, skipAdisFusion: true, force: true });
        if (apiAns?.content) sources.push({ lobe: 'api', content: apiAns.content, weight: 1, emb: embedLattice(apiAns.content) });
      } catch {}
    }
    for (const a of apis) {
      sources.push({ lobe: 'api:' + (a.model || a.baseUrl || 'x'), content: a.stubContent || '', weight: a.weight ?? 0.8, emb: embedLattice(a.stubContent || a.model || '') });
    }
    const fusion = unifiedMindFusionPlan(sources, {});
    this._adisPush?.({ type: 'fusion', msg: `lobes=${fusion.nSources} ${fusion.digest?.slice(0, 8)}` });
    await this.adisRemember('q:' + fusion.digest.slice(0, 12), fusion.content, {}).catch(() => {});
    return { ok: true, ...fusion, recall, tool: toolOut };
  }

  async slmBootstrap(manifestOrNull, opts = {}) {
    const man = slmManifestPlan(manifestOrNull || {
      name: 'adis-fixture-slm', bytes: 1024, format: 'fixture', hash: hex(sha256(Buffer.from('fixture-slm'))).slice(0, 32),
    });
    const boot = slmBootstrapPlan(man, { ...opts, maxLocalMb: ADIS.maxLocalMb });
    if (!boot.ok) return boot;
    this.state.slm = { ...boot, ok: true, ts: Date.now() };
    saveState(this.state);
    await this.webSet('adis:slm', this.state.slm).catch(() => {});
    this._adisPush?.({ type: 'slm', msg: 'bootstrap ' + man.name });
    // register flock capability
    if (this.state.flock) {
      this.state.flock.capabilities = [...new Set([...(this.state.flock.capabilities || []), boot.capability])];
      await this.webSet('ado:flock', this.state.flock).catch(() => {});
    }
    return this.state.slm;
  }

  async adisLoraMerge(deltas, opts = {}) {
    const list = (deltas || []).map((d) => d.weights || d);
    const avg = typeof fedAverage === 'function' ? fedAverage(list) : list[0];
    const plan = loraDeltaPlan(opts.base || {}, avg || {}, { scale: opts.scale ?? 1 });
    this.state.lora = plan;
    saveState(this.state);
    await this.webSet('adis:lora', plan).catch(() => {});
    this._adisPush?.({ type: 'lora', msg: 'merge n=' + list.length });
    return plan;
  }

  /** Immortal Vault — RS + hash-chain anti-revision */
  async vaultPut(name, data, opts = {}) {
    const blob = Buffer.isBuffer(data) ? data : Buffer.from(typeof data === 'string' ? data : JSON.stringify(data), 'utf8');
    const prev = this.webGet('vault:tip:' + (name || 'default')) || this.state.vaultTips?.[name || 'default'] || null;
    const sealed = vaultSeal(blob, prev, { k: opts.k, n: opts.n });
    const rev = antiRevisionGate({ ...sealed, prev: sealed.prev }, prev, {});
    if (!rev.ok && prev) return { ok: false, reason: rev.reason };
    const uploaded = [];
    for (let i = 0; i < (sealed.plan.sharesB64 || []).length; i++) {
      try {
        const buf = Buffer.from(sealed.plan.sharesB64[i], 'base64');
        const { hash } = await blossomUpload(buf, 'application/octet-stream');
        uploaded.push({ i, hash });
      } catch {}
    }
    const body = { ...sealed, shares: uploaded, name: name || 'default' };
    delete body.plan.sharesB64;
    this.state.vaultTips ??= {};
    this.state.vaultTips[name || 'default'] = body;
    saveState(this.state);
    await this.webSet('vault:tip:' + (name || 'default'), body).catch(() => {});
    await this.publishName('vault:' + (name || 'default'), 'vault:' + sealed.digest).catch(() => {});
    return { ok: true, digest: sealed.digest, merkleRoot: sealed.merkleRoot, shares: uploaded.length, seq: sealed.seq };
  }

  async vaultGet(name, opts = {}) {
    const tip = this.webGet('vault:tip:' + (name || 'default')) || this.state.vaultTips?.[name || 'default'];
    if (!tip?.digests) return { ok: false, reason: 'no-vault-tip' };
    const shares = [];
    for (const s of tip.shares || []) {
      try {
        const url = (BLOSSOM_SERVERS[0] || '') + '/' + s.hash;
        const res = await fetch(url);
        if (res.ok) shares[s.i] = Buffer.from(await res.arrayBuffer());
      } catch {}
    }
    const open = vaultOpen(shares, tip.plan || tip, opts);
    return { ...open, tip };
  }

  async vaultHeal(name, opts = {}) {
    const got = await this.vaultGet(name, opts);
    if (!got.ok) return got;
    return this.vaultPut(name, got.blob, opts);
  }

  /** Global Sub-Mind — פיצול משימות ל-visitor/node workers */
  async runSubMind(task, opts = {}) {
    const plan = subMindJobPlan(task, opts);
    const workers = this.state.workers || {};
    const assign = assignSubMindWorkers(plan.jobs, workers, opts);
    this.state.subMind ??= {};
    this.state.subMind[plan.root] = { plan, assign, ts: Date.now() };
    saveState(this.state);
    await this.webSet('submind:job:' + plan.root, { plan, assign, ts: Date.now() }).catch(() => {});
    // continuum item לכל job (receipt-ready)
    for (const a of assign.assigns.slice(0, 16)) {
      try {
        await this.enqueueMindContinuum?.({
          messages: [{ role: 'user', content: JSON.stringify(a.job.payload) }],
          worker: a.worker, digest: a.job.digest,
        });
      } catch {}
    }
    return { ok: true, root: plan.root, n: plan.n, assign };
  }

  /** מפעיל את כל שכבת עקיפת-המגבלות */
  async enablePhysicsLattice(opts = {}) {
    if (this._physicsLattice) return this.slaReport();
    this._physicsLattice = true;
    this.enableDbApi(opts);
    this.enablePhysicsLatticeApi(opts); // Ω: /api/sla · /api/compose/* גם בלי --mind-api
    this.enableAutonomicCapsuleHeal(opts.autoPull !== false); // S48
    if (opts.seedGossip !== false) this._autoSeedGossip = true; // S57
    await this.syncMeshPeers().catch(() => {});
    await this.beatWorker({ lattice: true }).catch(() => {});
    if (ADDRESS_STACK_AUTO || opts.address !== false) await this.ensureAddressStack(opts.address || {}).catch(() => {});
    if (CDN_LATTICE || opts.cdn !== false) await this.publishCdnLattice().catch(() => {});
    if (opts.seed) await this.publishVisitorSeed(opts.seed === true ? {} : opts.seed).catch(() => {});
    await this.ensureZeroOriginContinuity().catch(() => {});
    log('Physics Lattice: mesh+address+cdn+sla+S48+Ω compose פעילים.');
    return this.slaReport();
  }

  /**
   * Living Global Address — הכתובת הגלובלית מתנהגת כ-Node מלא (כמו חיבור CUSTOM_DOMAIN):
   * physics lattice + snapshots קריטיים + skylive living-mesh + מבקרים=עובדים.
   * כנה: השער לא מריץ Node; המוצר עוקף את זה ב-mesh/∇/snapshots.
   */
  async enableLivingAddress(opts = {}) {
    if (this._livingAddressReady && opts.force !== true) {
      return this.state.livingPlan || livingAddressPlan({ canonical: globalAddress(), domain: CUSTOM_DOMAIN || null });
    }
    this._livingAddress = true;
    this.state.livingAddress = true;
    saveState(this.state);

    if (!this._sitePolicies?.size && opts.policyKit !== false) {
      await this.publishPolicyKit({ site: opts.site || this._defaultLiveSite || 'pricing' }).catch(() => {});
    }

    await this.enablePhysicsLattice({
      address: opts.address === false ? false : (opts.address || { card: !!(opts.card || CUSTOM_DOMAIN) }),
      seed: opts.seed !== false,
      cdn: opts.cdn !== false,
      autoPull: opts.autoPull !== false,
    }).catch((err) => log('Living Address lattice:', err.message));

    if (opts.mind) {
      try {
        this.configureMind({ enabled: true });
        this.enableMindApi({ enabled: true });
        this.bindMindToPulse?.();
      } catch (err) { log('Living Address mind:', err.message); }
    }

    const snaps = await this.publishLivingSnapshots(opts).catch((err) => {
      log('Living Address snapshots:', err.message);
      return { paths: [], error: err.message };
    });
    await this.ensureZeroOriginContinuity().catch(() => {});

    const handle = this.state.skyHandle || SITE_SLUG;
    const addr = this.state.addressStack || addressStackPlan({
      canonical: globalAddress(),
      sky: 'sky://' + handle,
      at: globalAddress().replace(/\/$/, '') + '/@',
      domain: CUSTOM_DOMAIN || null,
      callingCard: this.state.callingCard || null,
      mirrors: mirrorAddresses(),
    });
    const sla = this.slaReport?.() || {};
    const plan = livingAddressPlan({
      ...addr,
      apiPrefix: API_PREFIX,
      snapshots: snaps.paths || null,
      workersAlive: sla.sla?.workersAlive ?? null,
    });
    const gate = livingAddressAntiStaticGate(
      { ...plan, hasCloudWeb: true, hasSkylive: true },
      [['/cloud-web.js'], ['/skylive.json']],
    );
    if (gate.staticCollision || !gate.ok) {
      throw new Error('Living Address anti-static failed: ' + (gate.reasons.join(',') || 'static-collision'));
    }

    this.state.livingPlan = plan;
    this.state.livingGate = gate;
    saveState(this.state);
    await this.webSet('address:living', plan).catch(() => {});
    await this.webSet('phi:living', { living: true, staticOnly: false, trustServer: false, ts: Date.now() }).catch(() => {});

    if (opts.republish !== false) {
      await this._publishSite().catch((err) => log('Living Address republish:', err.message));
      if (PUBLIC_WEBSITE) await this._publishWebsite().catch(() => {});
    }

    this._livingAddressReady = true;
    log(`Living Global Address: mode=${plan.mode} · staticOnly=false · mesh+∇+snapshots · gate=ok`);
    log('  קנונית:', plan.canonical || globalAddress());
    if (plan.domain) log('  דומיין (אותו נתיב חי):', plan.domain);
    else if (CUSTOM_DOMAIN) printDomainSetup();
    else log('  טיפ: CUSTOM_DOMAIN = אותו Living Address עם שם קצר (CNAME → npub gateway)');
    if (SIDE_EFFECT_DIAL || opts.dial !== false) {
      await this.openDedicatedMeshDial({ handle: this.state.skyHandle || SITE_SLUG }).catch(() => {});
    }
    if (SPACE_MESH.enabled && opts.space !== false) {
      await this.enableSpaceMesh({ force: true }).catch((err) => log('SpaceMesh:', err.message));
    }
    if (IMMORTAL_MESH.enabled && opts.immortal !== false) {
      await this.enableImmortalMesh({ force: true, skipPack: true }).catch((err) => log('Immortal:', err.message));
    }
    if (ADO.enabled && opts.ado !== false) {
      await this.enableADO({ force: true, skipPack: true }).catch((err) => log('ADO:', err.message));
    }
    const spaceAddr = await this.ensureSpaceAddressPresence({ force: true }).catch(() => null);
    return {
      ...plan, gate, snaps,
      dial: this.state.meshDial || null,
      space: this.state.spaceMesh || null,
      spacePresence: spaceAddr?.presence || this.state.spacePresence || null,
      immortal: this.state.immortalTip || null,
      ado: this.state.ado || null,
    };
  }

  /**
   * Side-Effect Node על הכתובת — Living + Full-Node/bridge + dial ייעודי.
   * מבקר בודד מתחבר בערוץ Ω; side-effects רצים אצל peers/visitor-workers/LOCAL_BRIDGE.
   */
  async enableSideEffectNode(opts = {}) {
    const living = await this.enableLivingAddress({ ...opts, force: !!opts.force, dial: true });
    if (opts.fullNode !== false && (opts.fullNode || FULL_NODE || opts.bridge || LOCAL_BRIDGE)) {
      if (opts.bridge || LOCAL_BRIDGE) this.bridge(opts.bridge || LOCAL_BRIDGE);
      else {
        try { await this.startFullNode({ port: opts.port || FULL_NODE_PORT, physics: false }); }
        catch (err) { log('Side-Effect Node: full-node optional —', err.message); }
      }
    }
    const dial = await this.openDedicatedMeshDial(opts.dial || {});
    log('Side-Effect Node: living+dial · staticOnly=false · side-effects via mesh/bridge');
    return { living, dial, bridge: this.bridgeUrl || null, fullNode: !!this._fullNodeServer };
  }

  /** פותח/מפרסם חיבור ייעודי למבקר (mesh dial) — S125 receipt לכל session */
  async openDedicatedMeshDial(opts = {}) {
    const handle = opts.handle || this.state.skyHandle || SITE_SLUG;
    const dial = dedicatedMeshDialPlan({ handle, channel: opts.channel });
    dial.canon = globalAddress();
    dial.sky = 'sky://' + handle;
    dial.apiPrefix = API_PREFIX;
    dial.ts = Date.now();
    const sess = dialSessionReceiptPlan(dial, { handle, visitor: opts.visitor || null });
    dial.sessionId = sess.sessionId;
    dial.receiptId = sess.receipt?.id || null;
    this.state.meshDial = dial;
    this.state.dialReceipts ??= {};
    if (sess.ok) this.state.dialReceipts[sess.sessionId] = sess.receipt;
    saveState(this.state);
    await this.webSet('mesh:dial', dial).catch(() => {});
    if (sess.ok) {
      await this.webSet('mesh:dial:receipt:' + sess.sessionId, sess.receipt).catch(() => {});
      await this.publishUiReceipt(sess.receipt, { site: handle }).catch(() => {});
    }
    await this.publishName('mesh-dial', dial.channel).catch(() => {});
    await this.beatWorker({ dial: dial.channel, sideEffect: true, sessionId: sess.sessionId }).catch(() => {});
    return { ...dial, receipt: sess.receipt || null, sessionId: sess.sessionId };
  }

  /** מפרסם תמונות-API קריטיות — כתובת חיה גם בלי אף עובד Node מחובר */
  async publishLivingSnapshots(opts = {}) {
    if (!this._dbApiEnabled) this.enableDbApi(opts);
    if (!this._physicsApi) this.enablePhysicsLatticeApi(opts);
    const sla = this.slaReport();
    const health = {
      living: true,
      node: true,
      mesh: true,
      trustServer: false,
      staticOnly: false,
      mode: 'living-mesh',
      db: this._skydb?.health?.() || null,
      sla: sla.sla || null,
      address: this.state.addressStack || null,
      ts: Date.now(),
    };
    const paths = ['/api/health', '/api/sla', '/api/trust', '/api/address', '/api/compose/status', '/api/lattice-wave/status', '/api/space/mesh', '/api/immortal/mesh'];
    await this.snapshot('/api/health', health);
    await this.snapshot('/api/sla', sla);
    await this.snapshot('/api/trust', {
      trustServer: false, living: true, staticOnly: false, model: 'visitor-verifies', mode: 'living-mesh',
    });
    await this.snapshot('/api/address', this.state.addressStack || addressStackPlan({
      canonical: globalAddress(), domain: CUSTOM_DOMAIN || null, sky: 'sky://' + (this.state.skyHandle || SITE_SLUG),
    }));
    await this.snapshot('/api/lattice-wave/status', {
      bft: this.bftStatus(),
      doors: this.latticeDoors(),
      ts: Date.now(),
    }).catch(() => {});
    await this.snapshot('/api/space/mesh', await this.spaceMeshReport().catch(() => ({ ok: false }))).catch(() => {});
    await this.snapshot('/api/immortal/mesh', {
      immortal: !!(typeof IMMORTAL_MESH !== 'undefined' && IMMORTAL_MESH),
      replicas: this.state.immortalReplicas || null,
      ts: Date.now(),
    }).catch(() => {});
    await this.snapshot('/api/compose/status', {
      living: true, mode: 'living-mesh', staticOnly: false, trustServer: false,
      doors: latticeWaveDoorsCatalog(),
      space: !!(typeof SPACE_MESH !== 'undefined' && SPACE_MESH),
      immortal: !!(typeof IMMORTAL_MESH !== 'undefined' && IMMORTAL_MESH),
      latticeWave: true,
      ts: Date.now(),
    }).catch(() => {});
    for (const p of paths) {
      const r = this.routes.find((x) => x.method === 'GET' && x.path === p);
      if (r) r.static = true;
    }
    await this.webSet('living:snaps', { paths, ts: Date.now() }).catch(() => {});
    return { paths, health };
  }

  enablePhysicsLatticeApi(opts = {}) {
    this.enableLatticeWaveApi(opts);
    if (this._physicsApi) return this;
    this._physicsApi = true;
    const json = (body, status = 200) => ({ status, headers: { 'content-type': 'application/json' }, body });
    const read = async (req) => {
      try { return typeof req.json === 'function' ? await req.json() : JSON.parse(req.body?.toString?.() || req.body || '{}'); }
      catch { return {}; }
    };

    this.route('GET', '/api/sla', async () => json(this.slaReport()));
    this.route('GET', '/api/mesh/workers', async () => json({ workers: this.state.workers || {}, peers: meshPeersFromRegistry(this.state.workers || {}) }));
    this.route('POST', '/api/mesh/beat', async () => json(await this.beatWorker()));
    this.route('POST', '/api/mesh/batch', async (req) => {
      const b = await read(req);
      try { return json(await this.meshBatch(b.ops || [], b)); }
      catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('GET', '/api/cdn', async () => json(this.state.cdnLattice || await this.publishCdnLattice()));
    this.route('POST', '/api/cdn/probe', async () => json(await this.probeCdnLattice()));
    this.route('GET', '/api/address', async () => json(this.state.addressStack || addressStackPlan({ canonical: globalAddress() })));
    this.route('POST', '/api/address/ensure', async (req) => json(await this.ensureAddressStack(await read(req))));
    this.route('GET', '/api/trust', async () => json({
      trustServer: false,
      model: 'visitor-verifies',
      stack: ['∇ client-eval', 'receipts', 'sealed-rows', 'pathDigest', 'context AAD'],
      zeroTrust: zeroTrustBundle({ verified: { match: true }, contextOk: true, source: 'client-eval' }),
    }));
    // S123 Living SLA Door Compose (+ S125–S136 / ADO)
    this.route('GET', '/api/doors/living', async () => {
      const sla = this.slaReport();
      const health = liveAddressHealthBadge(sla.sla || {});
      const sticky = failoverStickyTtlPlan(this.state.cdnFailover || this.webGet('cdn:failover'), Date.now());
      const pin = this.checkTrustRootCapsulePin?.(this.webGet('capsule:mesh:tip')) || null;
      const handle = this.state.skyHandle || SITE_SLUG;
      const dial = this.state.meshDial || dedicatedMeshDialPlan({ handle });
      const dialReceipt = dial.sessionId ? (this.state.dialReceipts?.[dial.sessionId] || this.webGet('mesh:dial:receipt:' + dial.sessionId)) : null;
      const body = livingSlaDoorCompose({
        health, workersAlive: health.workersAlive,
        failover: this.state.cdnProbe?.failover || null,
        sticky, pin, visitorCap: sla.visitorCap,
        dial, dialReceipt,
        fullApp: this.state.fullAppGate || this.webGet('address:full-app'),
        merkle: this.state.fullAppMerkle || this.webGet('address:merkle'),
        continuum: this.state.appContinuum || this.webGet('app:continuum'),
        organism: this.webGet('ado:quorum') || this.state.ado?.quorum,
        census: this.webGet('ado:census') || this.state.ado?.census,
        passport: this.webGet('ado:passport') || this.state.ado?.passport,
      });
      return json(body);
    });
    this.route('POST', '/api/mesh/dial', async (req) => {
      const b = await read(req);
      try { return json(await this.openDedicatedMeshDial(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    // SpaceMesh-Ν / NASA DTN control plane
    this.route('GET', '/api/space/mesh', async () => json(await this.spaceMeshReport()));
    this.route('POST', '/api/space/enable', async (req) => json(await this.enableSpaceMesh(await read(req))));
    this.route('GET', '/api/space/presence', async () => json({
      presence: this.state.spacePresence || this.webGet('space:presence'),
      address: this.webGet('address:space'),
      catalog: this.state.spaceCatalog || this.webGet('space:catalog'),
      hops: this.webGet('space:hops'),
      replicas: this.state.spaceReplicas || null,
      connectNotControl: true,
      note: 'Identify sat IDs → hop replicate → gossip next; no physical sat control',
      ts: Date.now(),
    }));
    this.route('POST', '/api/space/presence', async (req) => json(await this.ensureSpaceAddressPresence(await read(req))));
    this.route('POST', '/api/space/discover', async (req) => json(await this.discoverSpaceNodes(await read(req))));
    this.route('POST', '/api/space/hop', async (req) => json(await this.spaceHopReplicate(await read(req))));
    this.route('POST', '/api/space/continuations', async (req) => json(await this.runSpaceHopContinuations(await read(req))));
    this.route('GET', '/api/space/continuations', async () => json({
      cont: this.state.spaceHopCont || this.webGet('space:hop:continuations'),
      receipts: this.state.spaceHopReceipts || this.webGet('space:hop:receipts'),
      roam: this.state.addressRoam || this.webGet('address:roam'),
      vault: this.state.hopVault || this.webGet('space:hop:vault'),
      rain: this.webGet('space:hop:rain'),
      market: this.webGet('space:hop:market'),
      tipCutover: this.webGet('space:tip:cutover'),
      doors: 'S137-S150',
      ts: Date.now(),
    }));
    this.route('POST', '/api/space/dtn', async (req) => {
      const b = await read(req);
      try { return json(await this.dtnSend(b.payload ?? b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/space/custody', async () => json(await this.dtnCustodySweep()));
    this.route('POST', '/api/space/ratchet', async (req) => {
      const b = await read(req);
      try { return json(await this.militaryChannelStep(b.peer || b.pub, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    // Immortal-Ι — שכפול עצמי על אנטנות
    this.route('GET', '/api/immortal', async () => json({
      tip: this.state.immortalTip || this.webGet('immortal:tip'),
      replicas: this.state.immortalReplicas || this.webGet('immortal:replicas'),
      extinction: this.webGet('immortal:extinction'),
      paths: this.webGet('immortal:paths'),
      spacePresence: this.state.spacePresence || this.webGet('space:presence'),
      trustServer: false, originOptional: true, ts: Date.now(),
    }));
    this.route('POST', '/api/immortal/enable', async (req) => json(await this.enableImmortalMesh(await read(req))));
    this.route('POST', '/api/immortal/replicate', async (req) => json(await this.replicateSelf(await read(req))));
    this.route('POST', '/api/immortal/sweep', async (req) => json(await this.immortalSweep(await read(req))));
    // ADO / Vault / SubMind
    this.route('GET', '/api/ado', async () => json({
      ado: this.state.ado || null,
      members: this.state.adoMembers || this.webGet('ado:members'),
      census: this.webGet('ado:census'),
      passport: this.webGet('ado:passport'),
      quorum: this.webGet('ado:quorum'),
      trustServer: false, unkillable: true, ts: Date.now(),
    }));
    this.route('POST', '/api/ado/enable', async (req) => json(await this.enableADO(await read(req))));
    this.route('POST', '/api/vault/put', async (req) => {
      const b = await read(req);
      try { return json(await this.vaultPut(b.name || 'default', b.data ?? b.blob ?? b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('GET', '/api/vault/:name', async (req) => json(await this.vaultGet(req.params?.name || 'default')));
    this.route('POST', '/api/vault/heal', async (req) => {
      const b = await read(req);
      try { return json(await this.vaultHeal(b.name || 'default', b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/submind/run', async (req) => {
      const b = await read(req);
      try { return json(await this.runSubMind(b.task ?? b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/bridge/rendezvous', async (req) => {
      const b = await read(req);
      try { return json(this.pickBridgeRendezvous(b.bridges || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/quotes/clear', async (req) => {
      const b = await read(req);
      try {
        const clearing = quoteMarketClearingPlan(b.quotes || [], b);
        if (clearing.clear) {
          const settled = await this.settleReceiptMarket({ ...b, clearingDigest: clearing.digest });
          return json({ clearing, settled });
        }
        return json({ clearing });
      } catch (err) { return json({ error: err.message }, 400); }
    });
    // SIOS / ADIS
    this.route('GET', '/api/sios', async () => json({
      manifest: this.state.sios?.manifest || siosPluginManifest({}),
      plugins: this.state.siosPlugins || {},
      trustServer: false, ts: Date.now(),
    }));
    this.route('POST', '/api/sios/enable', async (req) => json(await this.enableSIOS(await read(req))));
    this.route('POST', '/api/sios/plugin', async (req) => {
      const b = await read(req);
      try { return json(await this.enablePlugin(b.name || b.plugin, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/sios/runtime/invoke', async (req) => {
      const b = await read(req);
      try { return json(await this.runtimeInvoke(b.bytes || b.wasm || b.b64, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/skydrive/put', async (req) => {
      const b = await read(req);
      try { return json(await this.skyDrivePut(b.path || b.name, b.data ?? b.blob ?? b.content, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('GET', '/api/skydrive', async (req) => {
      const p = (req.path || '').includes('?') ? new URL(req.path, 'http://x').searchParams.get('path') : null;
      return json(await this.skyDriveGet(p || '/'));
    });
    this.route('POST', '/api/gateway/rewrite', async (req) => {
      const b = await read(req);
      const plan = httpLatticeGatewayPlan(b.path || b.url || '/', b);
      return json(gatewayRewrite(plan, b));
    });
    this.route('GET', '/api/did', async () => json(this.state.didDoc || this.webGet('sios:did') || null));
    this.route('GET', '/api/adis/status', async () => json({
      ok: !!this._adis,
      kernel: this.state.adisKernel?.digest || null,
      flock: this.state.flock || this.webGet('ado:flock'),
      slm: this.state.slm || null,
      events: (this.state.adisEvents || []).slice(-20),
      trustServer: false, ts: Date.now(),
    }));
    this.route('GET', '/api/adis/stream', async () => json({
      events: (this.state.adisEvents || []).slice(-80),
      rain: (this.state.adisEvents || []).slice(-40).map((e) => e.line),
      ts: Date.now(),
    }));
    this.route('POST', '/api/adis/enable', async (req) => json(await this.enableADIS(await read(req))));
    this.route('POST', '/api/adis/ask', async (req) => {
      const b = await read(req);
      try { return json(await this.adisAsk(b.prompt || b.input || b.messages || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/adis/remember', async (req) => {
      const b = await read(req);
      try { return json(await this.adisRemember(b.key || 'm', b.text || b.content || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/adis/slm/bootstrap', async (req) => json(await this.slmBootstrap(await read(req))));
    this.route('POST', '/api/adis/lora', async (req) => {
      const b = await read(req);
      try { return json(await this.adisLoraMerge(b.deltas || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    // "SQL נראה חי" — אותו SkyDB חם מאחורי שם מוכר
    this.route('GET', '/api/sql/health', async () => json({
      sql: false, skydb: true, note: 'nsite gateway is static; hot DB is Ω worker SkyDB',
      db: this._skydb?.health?.() || null, workers: slaScoreFromWorkers(this.state.workers || {}).workersAlive,
    }));

    // S37–S42: Mesh × Capsule × Zero-Trust × Mind
    this.route('POST', '/api/compose/capsule/deploy', async (req) => {
      const b = await read(req);
      try { return json(await this.deployCapsuleToMesh(b.dir || b.src || SITE_DIR || 'site', b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/capsule/pull', async (req) => {
      const b = await read(req);
      try { return json(await this.pullCapsuleFromMesh(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/verified', async (req) => {
      const b = await read(req);
      try { return json(await this.askMindVerified(b.messages || b.prompt || b.input || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/mesh', async (req) => {
      const b = await read(req);
      try { return json(await this.askMindOnMesh(b.messages || b.prompt || b.input || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/trust/quorum', async (req) => {
      const b = await read(req);
      try { return json(await this.attestTrustQuorum(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/genome/swap', async (req) => {
      const b = await read(req);
      try { return json(await this.hotGenomeCapsuleSwap(b.genome || b.profile, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('GET', '/api/compose/status', async () => json({
      doors: ['S37','S38','S39','S40','S41','S42','S43','S44','S45','S46','S47','S48','S49','S50','S51','S52','S53','S54','S55','S56','S57','S58','S59','S60','S61','S62','S63','S64','S65','S66','S67','S68','S69','S70','S71','S72','S74','S75','S76','S77','S78','S79','S80','S81','S83','S84','S85','S86','S87','S88','S89','S90','S91','S92','S93','S94','S95','S96','S97','S98'],
      capsuleTip: this.webGet('capsule:mesh:tip') || this.state.capsuleMeshTip || null,
      canary: this.webGet('capsule:canary') || null,
      canaryTip: this.webGet('capsule:canary:tip') || null,
      genome: this._capsuleGenome || null,
      autoPullCapsule: !!this._autoPullCapsule,
      visitorSeed: this.state.visitorSeed || this.webGet('visitor:seed') || null,
      continuum: this.webGet('mind:continuum:queue') || null,
      trustRoot: this.webGet('trust:root') || null,
      settlement: this.webGet('mind:market:settlement') || null,
      earnings: this.webGet('mind:earnings') || null,
      settleEarnBridge: this.webGet('bridge:settle-earn') || null,
      federationSettlement: this.webGet('federation:settlement') || null,
      gossipWarm: this.webGet('cdn:gossip-warm') || null,
      meshPick: pickMeshMindWorker(this.state.workers || {}, { localId: DEVICE }),
      sla: slaScoreFromWorkers(this.state.workers || {}),
      ts: Date.now(),
    }));

    this.route('POST', '/api/compose/mind/distill', async (req) => {
      const b = await read(req);
      try { return json(await this.verifiedMindDistill(b.messages || b.prompt || b.input || b, b.name || 'mind-distill', b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/capsule/canary', async (req) => {
      const b = await read(req);
      try {
        if (b.rollback) return json(await this.rollbackCapsuleCanary(b));
        if (b.promote) return json(await this.promoteCapsuleCanary(b));
        return json(await this.capsuleCanaryMesh(b.dir || b.src || SITE_DIR || 'site', b));
      } catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/seed', async (req) => {
      const b = await read(req);
      try { return json(await this.publishVisitorSeed(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/pay', async (req) => {
      const b = await read(req);
      try { return json(await this.payMindWithReceipt(b.messages || b.prompt || b.input || b, b)); }
      catch (err) { return json({ error: err.message }, 403); }
    });
    this.route('POST', '/api/compose/federate', async (req) => {
      const b = await read(req);
      try { return json(await this.federateTrustTip(b.pubkey || b.foreignPub, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/zero-origin', async (req) => {
      const b = await read(req);
      try {
        if (b.merge) return json(await this.mergeMindOnReturn(b));
        return json(await this.zeroOriginMind(b.messages || b.prompt || b.input || b, b));
      } catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/priced', async (req) => {
      const b = await read(req);
      try { return json(await this.latencyPricedMind(b.messages || b.prompt || b.input || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/distill-cdn', async (req) => {
      const b = await read(req);
      try { return json(await this.publishQuorumDistillCdn(b.messages || b.prompt || b.input || b, b.name || 'mind-distill', b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/capsule/rollback', async (req) => {
      const b = await read(req);
      try { return json(await this.rollbackCapsuleCanary(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/settle', async (req) => {
      const b = await read(req);
      try { return json(await this.settleReceiptMarket(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/capsule/adopt', async (req) => {
      const b = await read(req);
      try { return json(await this.adoptForeignCapsule(b.pubkey || b.foreignPub, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/continuum', async (req) => {
      const b = await read(req);
      try {
        if (b.drain) return json(await this.drainMindContinuum(b));
        return json(await this.enqueueMindContinuum(b.messages || b.prompt || b.input || b, b));
      } catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/earnings', async (req) => {
      const b = await read(req);
      try { return json(await this.settleWorkerEarnings(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/seed/gossip', async (req) => {
      const b = await read(req);
      try { return json(await this.gossipVisitorSeeds(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/trust/rotate', async (req) => {
      const b = await read(req);
      try { return json(await this.rotateTrustRoot(b.newRoot || b.digest || b.root, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/bridge/settle-earn', async (req) => {
      const b = await read(req);
      try { return json(await this.bridgeSettlementEarnings(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/capsule/federated-canary', async (req) => {
      const b = await read(req);
      try { return json(await this.federatedCanaryMarket(b.pubkey || b.foreignPub, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/mind/continuum-cdn', async (req) => {
      const b = await read(req);
      try { return json(await this.drainDistillSeedContinuum(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/trust/pin-check', async (req) => {
      const b = await read(req);
      try { return json(this.checkTrustRootCapsulePin(b.tip || b, b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/seed/warm-cdn', async (req) => {
      const b = await read(req);
      try { return json(await this.warmGossipCdn(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/capsule/sla-promote', async (req) => {
      const b = await read(req);
      try { return json(await this.slaGatedPromote(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/settle/cross', async (req) => {
      const b = await read(req);
      try { return json(await this.clearCrossProjectReceipts(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });
    this.route('POST', '/api/compose/trust/heal', async (req) => {
      const b = await read(req);
      try { return json(await this.runAutonomicTrustHeal(b)); }
      catch (err) { return json({ error: err.message }, 400); }
    });

    log('Physics Lattice Ω: /api/sla · /api/mesh/* · /api/cdn · /api/compose/* · /api/trust');
    return this;
  }

  // ═══════════ S37–S42: Mesh × Capsule × Zero-Trust × SkyMind ═══════════

  /** S40: חתימת אימות על קפסולה */
  attestCapsule(embed = CAPSULE_EMBED) {
    const att = capsuleAttest(embed);
    if (!att.ok) return att;
    ensureId(); deriveKeys();
    const body = { ...att, author: keys?.pubkey || DEVICE, ts: Date.now() };
    body.id = hex(sha256(Buffer.from('skycap-att:' + xiCanon({ digest: body.digest, nFiles: body.nFiles, packedAt: body.packedAt })))).slice(0, 40);
    return body;
  }

  /**
   * S37: פריסת קפסולה לרשת עובדים — pack → tip ב-webkv/Roam → עובדים מושכים
   */
  async deployCapsuleToMesh(srcDir, opts = {}) {
    const stage = opts.stage || 'full'; // full | canary
    // S74 Address-Bound Capsule Tip
    const addr = this.state.addressStack || this.webGet('address:stack') || addressStackPlan({
      canonical: globalAddress(), sky: skyHandleOf(this.state.skyHandle || SITE_SLUG).sky,
      domain: CUSTOM_DOMAIN || null, handle: this.state.skyHandle || SITE_SLUG,
    });
    const addrGate = addressBoundCapsuleGate({
      handle: this.state.skyHandle || SITE_SLUG, sky: addr.sky, canon: addr.canonical, domain: addr.domain || CUSTOM_DOMAIN,
    }, opts);
    if (!addrGate.ok) return { ok: false, reason: addrGate.reason, addrGate };
    const packed = await this.packCapsule(srcDir, {
      out: opts.out || path.join(ROOT, (opts.name || SITE_SLUG || 'app') + (stage === 'canary' ? '.canary' : '.mesh') + '.sky.js'),
      keepId: opts.keepId !== false,
      name: opts.name,
    });
    const embed = {
      v: packed.v || 1,
      b64: packed.b64,
      outDir: packed.outDir || '.sky-unpacked',
      nFiles: packed.nFiles,
      packedAt: packed.packedAt,
      site: packed.site || SITE_SLUG,
    };
    if (!embed.b64) throw new Error('S37: pack לא החזיר CAPSULE_EMBED.b64');
    const att = this.attestCapsule(embed);
    let blob = null;
    try {
      const buf = fs.readFileSync(packed.out);
      if (buf.length > 8_000) {
        const up = await blossomUpload(buf, 'application/javascript');
        blob = { hash: up.hash, url: `${up.servers[0]}/${up.hash}` };
      }
    } catch {}
    const tip = {
      v: 1, digest: att.digest, att, blob, out: path.basename(packed.out),
      nFiles: packed.nFiles, site: SITE_SLUG, ts: Date.now(), author: keys?.pubkey || DEVICE,
      stage,
      trustRoot: this.state.trustRoot || this.webGet('trust:root')?.digest || null,
      handle: addrGate.handle || this.state.skyHandle || SITE_SLUG,
      canon: addrGate.canon || globalAddress(),
    };
    const tipKey = opts.tipKey || (stage === 'canary' ? 'capsule:canary:tip' : 'capsule:mesh:tip');
    if (stage === 'canary') {
      // לא כותבים ל-capsule:mesh:tip — heal ימשוך רק אצל canary worker
      this.state.capsuleCanaryTip = tip;
    } else {
      this.state.capsuleMeshTip = tip;
      await this.publishName('capsule:mesh', 'cap:' + att.digest).catch(() => {});
    }
    saveState(this.state);
    await this.webSet(tipKey, tip).catch(() => {});
    await this.beatWorker({ capsuleDigest: att.digest, stage }).catch(() => {});
    log(`S37 Capsule→Mesh: stage=${stage} digest=${att.digest.slice(0, 16)}… files=${packed.nFiles} tip=${tipKey}`);
    return { tip, packed, att, tipKey, stage };
  }

  /** S37/S40/S62: משיכת קפסולה + אימות digest + trust-root pin לפני unpack */
  async pullCapsuleFromMesh(opts = {}) {
    const tip = opts.tip || this.webGet('capsule:mesh:tip') || this.state.capsuleMeshTip;
    if (!tip?.digest) return { ok: false, reason: 'no-tip' };
    const activeRoots = this.state.trustRoots || this.webGet('trust:root')?.active || [];
    if (opts.skipTrustRoot && activeRoots.length && !opts.allowSkipTrustRoot) {
      return { ok: false, reason: 'skip-trust-root-forbidden', activeRoots };
    }
    const rootGate = trustRootCapsuleGate(tip, activeRoots, opts);
    if (!rootGate.ok) return { ok: false, reason: rootGate.reason, rootGate };
    let embed = opts.embed || null;
    if (!embed && tip.blob?.url) {
      try {
        const res = await fetch(tip.blob.url);
        if (!res.ok) throw new Error('blob HTTP ' + res.status);
        const src = await res.text();
        const m = src.match(/const CAPSULE_EMBED = (\{[\s\S]*?\});/);
        if (m) embed = JSON.parse(m[1]);
      } catch (err) { return { ok: false, reason: 'blob-fetch', error: err.message }; }
    }
    if (!embed?.b64) return { ok: false, reason: 'no-embed' };
    const att = capsuleAttest(embed);
    if (att.digest !== tip.digest && !opts.skipVerify) {
      return { ok: false, reason: 'digest-mismatch', expected: tip.digest, got: att.digest };
    }
    const unpacked = await this.unpackCapsule(embed);
    this.state.capsuleMeshPulled = { digest: att.digest, at: Date.now(), trustRoot: tip.trustRoot || null };
    saveState(this.state);
    await this.beatWorker({ capsuleDigest: att.digest, pulled: true }).catch(() => {});
    return { ok: true, ...unpacked, digest: att.digest, att, rootGate };
  }

  /** S38: Mind + אפס-אמון — תשובה + חבילת אימות למבקר */
  async askMindVerified(input, opts = {}) {
    const out = await this.askMind(input, { ...opts, signReceipt: opts.signReceipt !== false });
    const zt = mindZeroTrustPackage(out);
    // שמירה ל-webkv לאימות חוזר של מבקר
    if (out.digest) {
      await this.webSet('mind:zt:' + out.digest.slice(0, 24), {
        digest: out.digest, content: out.content, receipt: out.receipt, source: out.source, ts: Date.now(),
      }).catch(() => {});
    }
    return { ...out, zeroTrust: zt };
  }

  /**
   * S39: Mind על ה-mesh — בוחר עובד לפי SLA; אם אנחנו הנבחרים — מריצים כאן
   * (עובדים מרוחקים נחשפים ב-tip; הפעלה מלאה אצלם = אותו CLI על המכונה שלהם)
   */
  async askMindOnMesh(input, opts = {}) {
    await this.syncMeshPeers().catch(() => {});
    const pick = pickMeshMindWorker(this.state.workers || {}, {
      prefer: opts.prefer || 'lag', localId: DEVICE,
    });
    const localBest = !pick.worker || pick.worker.id === DEVICE || opts.forceLocal;
    if (!localBest) {
      // מפרסמים בקשת-כוונה לעובד הנבחר (intent) — הוא יכול למשוך ב-heal
      const job = {
        v: 1, type: 'mind', to: pick.worker.id, input: typeof input === 'string' ? input : input,
        opts: { thread: opts.thread, model: opts.model }, ts: Date.now(), from: DEVICE,
      };
      job.id = hex(sha256(Buffer.from('mindjob:' + xiCanon(job)))).slice(0, 32);
      await this.webSet('mind:job:' + job.id, job).catch(() => {});
      await this.webSet('mind:job:tip', { id: job.id, to: pick.worker.id, ts: job.ts }).catch(() => {});
      return {
        ok: true, deferred: true, jobId: job.id, assigned: pick.worker.id,
        reason: 'routed-to-mesh-worker', pick, zeroTrust: { trustServer: false, verify: ['poll mind:zt:*'] },
      };
    }
    const out = await this.askMindVerified(input, opts);
    return { ...out, assigned: DEVICE, pick, deferred: false };
  }

  /** עובד מושך jobs שיועדו אליו (נקרא מ-heal) — S75: בלי קבלה לא מפרסמים result */
  async drainMindMeshJobs(opts = {}) {
    const tip = this.webGet('mind:job:tip');
    if (!tip?.id) return { n: 0 };
    const job = this.webGet('mind:job:' + tip.id);
    if (!job || job.to !== DEVICE) return { n: 0, skipped: true };
    if (this.state.mindJobsDone?.[job.id]) return { n: 0, already: true };
    const out = await this.askMindVerified(job.input, { ...(job.opts || {}), ...(opts || {}) });
    if (!out.ok || out.content == null) return { n: 0, jobId: job.id, reason: 'mind-failed' };
    let pay = null;
    if (opts.requireReceipt !== false) {
      // S93 Mesh Job Escrow — בזמן canary התשלום נכנס escrow (דרך payMindWithReceipt)
      pay = await this.payMindWithReceipt(job.input, {
        priced: { ...out, ok: true, assigned: DEVICE, price: out.price || { usd: 0, tier: 'mesh-job' }, source: 'provider' },
        skipPay: true, autoPay: false, allowOfflinePay: !!opts.allowOfflinePay,
      }).catch((e) => ({ ok: false, error: e.message }));
      if (!pay?.ok) {
        await this.webSet('mind:job:unpaid:' + job.id, { digest: out.digest, error: pay?.error || pay?.reason, ts: Date.now() }).catch(() => {});
        return { n: 0, jobId: job.id, reason: 'mesh-job-unpaid', pay };
      }
    }
    this.state.mindJobsDone ??= {};
    this.state.mindJobsDone[job.id] = { at: Date.now(), digest: out.digest, paid: !!pay?.ok };
    saveState(this.state);
    await this.webSet('mind:job:result:' + job.id, {
      digest: out.digest, content: out.content, zeroTrust: out.zeroTrust, receiptId: pay?.pay?.receiptId || null, ts: Date.now(),
    }).catch(() => {});
    return { n: 1, jobId: job.id, digest: out.digest, pay };
  }

  /** S41: החלפת genome חמה — פרופיל קפסולה בלי ריסטארט מלא */
  async hotGenomeCapsuleSwap(genome, opts = {}) {
    const g = genome || capsuleGenomeSelect(this.scenarios?.last || {}, this._capsuleGenomeMap);
    this._capsuleGenome = g;
    await this.publishName('capsule:profile', g).catch(() => {});
    await this.webSet('capsule:genome', { profile: g, ts: Date.now(), hot: true }).catch(() => {});
    await this.webSet('app:ui', {
      profile: g === 'offline-nabla' || g === 'degraded' ? 'degraded' : 'full',
      genome: g, ts: Date.now(),
    }).catch(() => {});
    // אם יש tip קפסולה ל-mesh ו-genome דורש offline — continuity
    if (g === 'offline-nabla') await this.ensureZeroOriginContinuity({ genome: g }).catch(() => {});
    if (opts.pullCapsule) await this.pullCapsuleFromMesh(opts).catch(() => {});
    await this.beatWorker({ genome: g }).catch(() => {});
    return { ok: true, genome: g };
  }

  /** S42: קוורום אמון על digest — בקשת mesh + איסוף beats + webkv (לא self-N בלי allowSelfQuorum) */
  async attestTrustQuorum(opts = {}) {
    const digest = opts.digest;
    if (!digest) throw new Error('S42: חסר digest');
    ensureId(); deriveKeys();
    if (opts.request !== false) {
      await this.webSet('trust:req:' + String(digest).slice(0, 32), {
        digest, kind: opts.kind || 'generic', need: opts.quorum || 2, from: DEVICE, ts: Date.now(),
      }).catch(() => {});
    }
    const self = {
      digest, worker: DEVICE, kind: opts.kind || 'generic', ts: Date.now(),
      pubkey: keys?.pubkey || null,
      commit: hex(sha256(Buffer.from('trust-att:v1:' + digest + ':' + DEVICE))).slice(0, 32),
    };
    const key = 'trust:att:' + String(digest).slice(0, 32);
    const prev = this.webGet(key) || { attestations: [] };
    let attestations = [...(prev.attestations || []).filter((a) => a.worker !== DEVICE && !a.synthetic), self];
    await this.syncMeshPeers().catch(() => {});
    const workers = this.state.workers || this.webGet('mesh:workers') || {};
    for (const w of Object.values(workers)) {
      if (!w?.id || w.id === DEVICE) continue;
      if (w.attestDigest === digest || w.capsuleDigest === digest || w.mindDigest === digest) {
        if (!attestations.some((a) => a.worker === w.id)) {
          attestations.push({ digest, worker: w.id, kind: opts.kind || 'generic', ts: w.ts || Date.now(), fromBeat: true });
        }
      }
    }
    if (opts.allowSelfQuorum) {
      for (let i = attestations.length; i < (opts.quorum || 2); i++) {
        attestations.push({ digest, worker: 'self-' + i, kind: opts.kind || 'generic', ts: Date.now(), synthetic: true });
      }
    }
    let q = trustMeshQuorum(attestations, opts.quorum || 2);
    if (!q.ok && (opts.waitMs || 0) > 0) {
      await new Promise((r) => setTimeout(r, Math.min(opts.waitMs, 4000)));
      const again = this.webGet(key);
      if (again?.attestations?.length) {
        attestations = [...again.attestations.filter((a) => a.worker !== DEVICE && !a.synthetic), self];
        if (opts.allowSelfQuorum) {
          for (let i = attestations.length; i < (opts.quorum || 2); i++) {
            attestations.push({ digest, worker: 'self-' + i, kind: opts.kind || 'generic', ts: Date.now(), synthetic: true });
          }
        }
        q = trustMeshQuorum(attestations, opts.quorum || 2);
      }
    }
    const body = { digest, attestations, quorum: q, ts: Date.now() };
    await this.webSet(key, body).catch(() => {});
    await this.webSet('trust:quorum:tip', { digest, ok: q.ok, agree: q.agree, ts: Date.now() }).catch(() => {});
    await this.beatWorker({ attestDigest: digest }).catch(() => {});
    return body;
  }

  /** S20/S54: משיכת מפתח webkv מפרויקט זר (author=foreignPub) */
  async fetchForeignWebKey(pubkey, key, waitMs = 4000) {
    if (!this._starting && !this._started) await this.ready();
    else { ensureId(); deriveKeys(); }
    const author = String(pubkey);
    const d = `scweb:${author.slice(0, 16)}:${key}`;
    return new Promise((resolve) => {
      let found = null; let foundTs = 0;
      const sub = this.pool.subscribe(
        [{ kinds: [KIND_DATA], authors: [author], '#d': [d], limit: 3 }],
        (ev) => {
          try {
            if (verifyEvent(ev) !== true) return;
            const entry = JSON.parse(ev.content);
            if (typeof entry?.ts === 'number' && entry.ts >= foundTs) {
              foundTs = entry.ts; found = entry.v;
            }
          } catch {}
        },
        () => {},
      );
      setTimeout(() => { try { sub?.close?.(); } catch {} resolve(found); }, waitMs);
    });
  }

  // ═══════════ S43–S58: Mesh×Capsule×ZT×Mind (hardened) + Next Compose ═══════════

  /**
   * S43 Verified Mind Distill — LLM → בקשת קוורום mesh → echoDistill רק אם ≥k
   */
  async verifiedMindDistill(input, name, opts = {}) {
    const ask = await this.askMindVerified(input, { ...opts, signReceipt: true });
    if (!ask.ok || !ask.content) return { ok: false, reason: 'mind-failed', ask };
    await this.beatWorker({ mindDigest: ask.digest }).catch(() => {});
    const att = await this.attestTrustQuorum({
      digest: ask.digest, kind: 'mind', quorum: opts.quorum || 2,
      allowSelfQuorum: !!opts.allowSelfQuorum, waitMs: opts.waitMs || 1500,
    });
    if (!att.quorum?.ok) {
      return { ok: false, reason: 'quorum-pending', ask, attest: att, distilled: null };
    }
    const pub = await this.echoDistill(name || 'mind-distill', ask.content, { digest: ask.digest });
    const verified = {
      name: pub.name || name, dagId: pub.dagId, digest: ask.digest,
      content: String(ask.content).slice(0, 8000), agree: att.quorum.agree, ts: Date.now(),
    };
    await this.webSet('distill:verified:' + (ask.digest || '').slice(0, 24), verified).catch(() => {});
    await this.webSet('distill:verified:tip', verified).catch(() => {});
    return { ok: true, ask, attest: att, distilled: pub, verified };
  }

  /**
   * S44 Capsule Canary Mesh — tip נפרד (capsule:canary:tip); heal מושך רק אצל canary worker
   */
  async capsuleCanaryMesh(srcDir, opts = {}) {
    await this.syncMeshPeers().catch(() => {});
    // S98 Multi-Canary Shard Rollout — canary לפי shard owners כש-byShard
    const plan = opts.byShard
      ? multiCanaryShardPlan(this.state.workers || {}, { nShards: opts.nShards })
      : capsuleCanaryPlan(this.state.workers || {}, { promote: false });
    const prev = this.webGet('capsule:mesh:tip') || this.state.capsuleMeshTip || null;
    const dep = await this.deployCapsuleToMesh(srcDir, {
      ...opts, stage: 'canary', name: (opts.name || SITE_SLUG) + '-canary',
    });
    if (dep.ok === false) return dep;
    // רק canary / shard-canaries מחליפים genome מקומית
    const amCanary = (Array.isArray(plan.canaries) && plan.canaries.includes(DEVICE))
      || !plan.canary?.id || plan.canary.id === DEVICE;
    if (amCanary) await this.hotGenomeCapsuleSwap('degraded', { pullCapsule: false }).catch(() => {});
    const meta = {
      digest: dep.tip.digest,
      stage: plan.stage === 'canary-sharded' ? 'canary-sharded' : 'canary',
      canary: plan.canary, canaries: plan.canaries || null, pending: plan.pending || [],
      byShard: plan.byShard || null,
      prevDigest: prev?.digest || null, prevTip: prev || null, ts: Date.now(),
    };
    await this.webSet('capsule:canary', meta).catch(() => {});
    this.state.capsuleCanary = meta;
    // S108: canary → SkyDB read-only עד promote
    this._canarySqlReadOnly = true;
    if (this._skydb && opts.readOnly !== false) {
      try { this._skydb._freezeWrites = true; } catch {}
    }
    this._autoPullCapsule = true;
    if (amCanary) {
      const pr = await this.pullCapsuleFromMesh({ tip: dep.tip });
      if (!pr.ok && opts.rollbackOnFail !== false) {
        return this.rollbackCapsuleCanary({ reason: 'canary-pull-fail', pull: pr, meta });
      }
    }
    if (opts.promote || opts.autoPromote) {
      return this.promoteCapsuleCanary({ digest: dep.tip.digest, ...opts });
    }
    return { ok: true, stage: 'canary', plan, tip: dep.tip, dep, isolated: true, amCanary };
  }

  async promoteCapsuleCanary(opts = {}) {
    const canaryTip = this.webGet('capsule:canary:tip') || this.state.capsuleCanaryTip;
    const meta = this.webGet('capsule:canary') || {};
    const digest = opts.digest || canaryTip?.digest || meta.digest;
    if (!digest) throw new Error('S44: אין canary tip');
    await this.syncMeshPeers().catch(() => {});
    // S67 Dual-Root Promote Block
    const trustState = this.webGet('trust:root') || { phase: null, active: this.state.trustRoots || [] };
    const dual = dualRootPromoteGate(trustState, canaryTip || { digest, trustRoot: trustState.digest }, opts);
    if (!dual.ok) return { ok: false, reason: dual.reason, dual, trustState };
    // S64: SLA-Gated Promote
    const sla = slaScoreFromWorkers(this.state.workers || this.webGet('mesh:workers') || {});
    const gate = slaPromoteGate(sla, { minSla: opts.minSla ?? 50 });
    if (!gate.ok && !opts.forcePromote) {
      if (opts.rollbackOnFail !== false) {
        const rb = await this.rollbackCapsuleCanary({ reason: gate.reason, sla });
        return { ok: false, reason: gate.reason, gate, sla, rolledBack: rb };
      }
      return { ok: false, reason: gate.reason, gate, sla };
    }
    // S71/S90 Promote Earnings Hold — גם מאפס streak של auto-promote
    if (!this._skydb && this._dbApiEnabled) { /* ok */ }
    if (this._skydb) {
      const paid = this.db.query('mind:paid', '');
      const hold = promoteEarningsHoldGate(paid, digest, opts);
      if (!hold.ok) {
        this._promoteSlaStreak = 0; // S90
        await this.webSet('heal:promote-streak', { streak: 0, reason: 'promote-earnings-hold', ts: Date.now() }).catch(() => {});
        return { ok: false, reason: hold.reason, hold };
      }
    }
    // S95/S101: quorum + attest לפני כתיבת tip
    const att = await this.attestTrustQuorum({
      digest, kind: 'capsule-promote', quorum: opts.quorum || 2,
      allowSelfQuorum: !!opts.allowSelfQuorum, waitMs: opts.waitMs || 800,
    });
    if (!att.quorum?.ok && !opts.forcePromote) {
      return { ok: false, reason: 'promote-attest-pending', attest: att };
    }
    const shardQ = shardCanaryPromoteQuorum(meta, att.attestations || att.quorum?.attestations || [], { digest, requireAlways: !!opts.requireShardQuorum });
    if (!shardQ.ok && !opts.forcePromote) {
      return { ok: false, reason: shardQ.reason, shardQuorum: shardQ, attest: att };
    }
    const fullTip = {
      ...(canaryTip || {}), digest, stage: 'full', promote: true, ts: Date.now(),
      author: keys?.pubkey || DEVICE,
      trustRoot: (canaryTip && canaryTip.trustRoot) || this.state.trustRoot || this.webGet('trust:root')?.digest || null,
    };
    const plan = capsuleCanaryPlan(this.state.workers || {}, { promote: true });
    this.state.capsuleMeshTip = fullTip;
    saveState(this.state);
    await this.webSet('capsule:mesh:tip', fullTip).catch(() => {});
    await this.publishName('capsule:mesh', 'cap:' + digest).catch(() => {});
    await this.hotGenomeCapsuleSwap('full', { pullCapsule: !!opts.pullCapsule }).catch(() => {});
    await this.replicateSelf({ tip: fullTip }).catch(() => {});
    this._autoPullCapsule = true;
    // S108: יציאה מ-read-only אחרי promote
    this._canarySqlReadOnly = false;
    if (this._skydb?._freezeWrites && opts.unfreezeOnPromote !== false) {
      try { this._skydb.unfreezeWrites(); } catch {}
    }
    // S72: שחרור escrow canary → released
    let settle = null;
    if (this._skydb) {
      for (const r of this.db.query('mind:paid', '')) {
        if (r.value?.escrow === 'canary') {
          try {
            await this.db.put('mind:paid', r.key, canaryEarningsEscrowPlan(r.value, 'released'), { force: true });
          } catch {}
        }
      }
      // S85 Escrow→Settlement Auto
      if (opts.autoSettle !== false) {
        settle = await this.settleReceiptMarket({ bridgeEarnings: true, allowReplay: true }).catch((e) => ({ ok: false, error: e.message }));
      }
    }
    await this.webSet('capsule:canary', { ...meta, digest, stage: 'full', plan, slaGate: gate, dual, attest: att.quorum, shardQuorum: shardQ, ts: Date.now() }).catch(() => {});
    await this.beatWorker({ capsuleDigest: digest, canary: 'full', attestDigest: digest }).catch(() => {});
    this._promoteSlaStreak = 0;
    return { ok: true, stage: 'full', digest, plan, tip: fullTip, gate, sla, dual, attest: att, shardQuorum: shardQ, settle };
  }

  /** S52 Canary Rollback — tip ישן חוזר ל-mesh + genome degraded */
  async rollbackCapsuleCanary(opts = {}) {
    const meta = opts.meta || this.webGet('capsule:canary') || {};
    const prev = meta.prevTip || (meta.prevDigest ? { digest: meta.prevDigest } : null);
    const plan = canaryRollbackPlan(meta, opts.sla || slaScoreFromWorkers(this.state.workers || {}), opts.pull, opts);
    if (prev?.digest) {
      const restore = { ...prev, stage: 'full', rolledBack: true, ts: Date.now() };
      this.state.capsuleMeshTip = restore;
      saveState(this.state);
      await this.webSet('capsule:mesh:tip', restore).catch(() => {});
    }
    await this.hotGenomeCapsuleSwap(plan.genome || 'degraded', { pullCapsule: false }).catch(() => {});
    // S72: forfeit escrow על rollback
    if (this._skydb) {
      for (const r of this.db.query('mind:paid', '')) {
        if (r.value?.escrow === 'canary') {
          try {
            await this.db.put('mind:paid', r.key, canaryEarningsEscrowPlan(r.value, 'forfeited'), { force: true });
          } catch {}
        }
      }
    }
    await this.webSet('capsule:canary', {
      ...meta, stage: 'rolled-back', reason: opts.reason || plan.reasons.join(',') || 'rollback',
      restoreDigest: prev?.digest || null, ts: Date.now(),
    }).catch(() => {});
    this._canarySqlReadOnly = false;
    if (this._skydb?._freezeWrites) {
      try { this._skydb.unfreezeWrites(); } catch {}
    }
    // S121 Card Revoke on Seed Fail — אם calling-card הגיע מ-seed
    let cardRevoke = null;
    try {
      const seed = this.webGet('visitor:seed') || (this.state.visitorSeed ? { url: this.state.visitorSeed } : null);
      cardRevoke = cardRevokeOnSeedFailPlan(seed, this.state.callingCard, { rolledBack: true, failed: true });
      if (cardRevoke.revoke) {
        this.state.callingCard = null;
        saveState(this.state);
        await this.webSet('calling-card:revoked', { ...cardRevoke, ts: Date.now() }).catch(() => {});
        await this.publishName('calling-card', 'revoked').catch(() => {});
      }
    } catch {}
    await this.beatWorker({ canary: 'rolled-back', capsuleDigest: prev?.digest || null }).catch(() => {});
    this._promoteSlaStreak = 0;
    log('S52 Canary Rollback:', opts.reason || plan.reasons.join(','));
    return { ok: true, rolledBack: true, restoreDigest: prev?.digest || null, plan, reason: opts.reason, cardRevoke };
  }

  /**
   * S45 Visitor Seed Capsule — HTML ב-Blossom + tip webkv (כולל distill מאומת אם יש)
   */
  async publishVisitorSeed(opts = {}) {
    ensureId(); deriveKeys();
    const h = skyHandleOf(opts.handle || this.state.skyHandle || SITE_SLUG);
    const canon = globalAddress();
    const site = opts.site || this._defaultLiveSite || 'pricing';
    const verTip = this.webGet('distill:verified:tip');
    // S97 Genome-Locked Seed — ב-offline-nabla אין allowUnverified
    const genome = this._capsuleGenome || this.webGet('capsule:genome')?.profile || 'full';
    const gLock = genomeLockedSeedGate(genome, verTip, opts);
    if (!gLock.ok) return { ok: false, reason: gLock.reason, gLock };
    // S84 Verified Seed Only
    const seedGate = verifiedSeedGate(verTip || (opts.distillName ? this.webGet('distill:verified:' + String(opts.distillName).slice(0, 24)) : null), {
      ...opts, allowUnverified: genome === 'offline-nabla' ? false : opts.allowUnverified,
    });
    if (!seedGate.ok && !opts.allowUnverified) {
      return { ok: false, reason: seedGate.reason, seedGate, hint: 'verifiedMindDistill / quorum first' };
    }
    // S88 Shadow Seed Gate
    if (verTip?.content && opts.skipShadow !== true) {
      const shadow = shadowPricedMindGate(verTip.content, opts.shadowPolicyOutput ?? 1, opts);
      if (!shadow.ok) return { ok: false, reason: 'shadow-seed-deny', shadow };
    }
    const distill = opts.distillName
      ? this.webGet('distill:' + slugify(opts.distillName))
      : (verTip || this.webGet('distill:mind-distill') || null);
    const html = Buffer.from(`<!DOCTYPE html><html lang="he" dir="rtl"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>${h.sky} seed</title>
<style>body{font-family:Georgia,serif;background:#0b0e14;color:#e6e9ef;padding:32px 16px;max-width:640px;margin:0 auto}
a{color:#7aa2f7}#out{white-space:pre-wrap;font:13px/1.4 ui-monospace,monospace;margin-top:12px}</style></head><body>
<h1>${h.sky}</h1>
<p>Visitor Seed — ∇ offline. Node לא חובה. <a href="${canon}">canonical</a></p>
<div id="skycloud-live" data-skysite="${site}" data-skylive="1" data-skyhandle="${h.handle}" data-seed="1"></div>
<pre id="out">טוען…</pre>
<script src="${canon.replace(/\/$/, '')}/cloud-web.js"><\/script>
<script>
(async () => {
  const out = document.getElementById('out');
  try {
    await window.SkyCloud?.ready;
    const dist = ${distill ? JSON.stringify({ dagId: distill.dagId, digest: distill.digest, name: distill.name }) : 'null'};
    if (dist && dist.dagId) out.textContent = 'seed distill tip dag=' + dist.dagId;
    const r = await window.SkyCloud.resolveLive('${site}', { n: 10 }, { verify: true });
    const zt = await window.SkyCloud.verifyLive(r);
    out.textContent = JSON.stringify({ output: r.output, zeroTrust: zt, seed: true, distill: dist }, null, 2);
  } catch (e) { out.textContent = 'seed ∇: ' + (e.message || e); }
})();
<\/script></body></html>`, 'utf8');
    const { hash, servers } = await blossomUpload(html, 'text/html');
    const url = `${servers[0]}/${hash}`;
    await this.publishName('visitor-seed', url);
    const body = {
      url, hash, handle: h.handle, site,
      digest: distill?.digest || verTip?.digest || null,
      agree: verTip?.agree || seedGate.agree || null,
      verified: true, ts: Date.now(),
    };
    this.state.visitorSeed = url;
    saveState(this.state);
    await this.webSet('visitor:seed', body).catch(() => {});
    await this.webSet('visitor:seed:gossip', { local: body, mirrors: [body], ts: Date.now() }).catch(() => {});
    // S109 Seed↔Calling-Card Sync — רק אם verified
    const cardGate = seedCallingCardSyncGate(body, opts);
    let cardSynced = false;
    if (cardGate.sync && (opts.syncCallingCard !== false) && (!this.state.callingCard || opts.replaceCallingCard)) {
      this.state.callingCard = url;
      saveState(this.state);
      cardSynced = true;
      await this.publishName('calling-card', url).catch(() => {});
    } else if (!this.state.callingCard && opts.allowUnverifiedCard) {
      this.state.callingCard = url;
      saveState(this.state);
      cardSynced = true;
    }
    log(`S45 Visitor Seed: ${url}` + (cardSynced ? ' · calling-card synced' : ''));
    return { ok: true, ...body, ...h, canon, site, seedGate, cardGate, cardSynced };
  }

  /**
   * S46 Receipt-Market Mesh — קבלה חתומה + ledger mind:paid (נכשל בקול אם DB לא זמין)
   */
  async payMindWithReceipt(input, opts = {}) {
    const priced = opts.priced
      || await this.latencyPricedMind(input, { ...opts, skipPay: true, autoPay: false });
    if (!priced.ok || priced.content == null) return { ok: false, reason: 'no-content', priced };
    // S83 Zero-Origin Pay Block
    const zoPay = zeroOriginPayGate(priced.source, opts);
    if (!zoPay.ok) return { ok: false, reason: zoPay.reason, priced, zoPay };
    const preview = String(priced.content).slice(0, 200);
    const worker = priced.assigned || DEVICE;
    const body = makeReceiptBody(
      'mindpay:' + (priced.digest || 'x'),
      { digest: priced.digest, worker, tier: priced.price?.tier, usd: priced.price?.usd },
      preview,
      { visitor: opts.visitor || keys?.pubkey },
    );
    const signed = await this.signReceipt(body);
    const pay = await this.creditFromReceipt(signed, preview, {
      site: 'mind-market', marketBest: true, allowReplay: !!opts.allowReplay,
    });
    if (!this._skydb && !this._dbApiEnabled) this.enableDbApi();
    const rowKey = (priced.digest || body.id || Date.now().toString(36)).slice(0, 32);
    const canaryMeta = this.webGet('capsule:canary');
    let row = { worker, digest: priced.digest, price: priced.price, pay, receiptId: body.id, ts: Date.now() };
    // S72 Canary Earnings Escrow
    if (canaryMeta?.stage === 'canary') {
      row = canaryEarningsEscrowPlan({ ...row, canaryDigest: canaryMeta.digest }, 'canary');
    }
    await this.db.put('mind:paid', rowKey, row, { force: true });
    await this.webSet('mind:market:last', row).catch(() => {});
    return { ok: true, priced, pay, receipt: signed, worker, rowKey, escrow: row.escrow || null };
  }

  /**
   * S47 Cross-Project Trust Federation — tip זר + קוורום + pin חובה (אחרת metadata-only)
   */
  async federateTrustTip(foreignPub, opts = {}) {
    const tip = await this.fetchForeignDbTip(foreignPub, opts.waitMs || 5000);
    if (!tip?.digest) throw new Error('S47: tip זר חסר');
    const att = await this.attestTrustQuorum({
      digest: tip.digest, kind: 'foreign-db', quorum: opts.quorum || 2,
      allowSelfQuorum: !!opts.allowSelfQuorum, waitMs: opts.waitMs || 1500,
    });
    if (!att.quorum?.ok) {
      return { ok: false, reason: 'quorum-pending', tip, attest: att };
    }
    const pin = opts.pin !== false; // ברירת מחדל: pin אמיתי
    if (!pin && !opts.allowMetadataOnly) {
      return { ok: false, reason: 'pin-required', tip, attest: att, hint: 'pass pin:true or allowMetadataOnly' };
    }
    const applied = await this.applyForeignCheckpoint(tip, {
      pin, freeze: !!opts.freeze, replayWal: !!opts.replayWal,
    });
    let policy = null;
    if (opts.policyName) {
      try {
        policy = await this.adoptForeignPolicy(foreignPub, opts.policyName, {
          quorum: opts.quorum || 2, allowSelfQuorum: !!opts.allowSelfQuorum, as: opts.as, inputs: opts.inputs,
        });
      } catch (err) { policy = { ok: false, error: err.message }; }
    }
    const out = { ok: true, tip, attest: att, applied, policy, pinned: !!applied.pinned };
    await this.webSet('federation:tip', { foreignPub, digest: tip.digest, ...out, ts: Date.now() }).catch(() => {});
    return out;
  }

  /** S48 Autonomic Capsule Healing */
  enableAutonomicCapsuleHeal(on = true) {
    this._autoPullCapsule = !!on;
    return { autoPullCapsule: this._autoPullCapsule };
  }

  /**
   * S49 Zero-Origin Mind — cache/distill בלבד כשאין Node; miss → תור continuum (S55)
   */
  async zeroOriginMind(input, opts = {}) {
    const cfg = { ...MIND, ...(this._mindCfg || {}) };
    const messages = mindNormalizeMessages(input, opts.system || cfg.system);
    const model = opts.model || cfg.model;
    const digest = mindPromptDigest(messages, model, { thread: opts.thread || 'default' });
    try {
      if (!this._skydb && this._dbApiEnabled) { /* db ready */ }
      const cached = this._skydb ? this.db.get('mind:cache', digest) : null;
      if (cached?.content && (!cached.exp || cached.exp > Date.now())) {
        return { ok: true, content: cached.content, source: 'zero-origin-cache', digest, costUsd: 0, zeroTrust: mindZeroTrustPackage({ ok: true, content: cached.content, digest, source: 'cache' }) };
      }
    } catch {}
    const tip = this.webGet('distill:verified:' + digest.slice(0, 24))
      || this.webGet('mind:zt:' + digest.slice(0, 24))
      || this.webGet('distill:verified:tip');
    if (tip?.content) {
      return { ok: true, content: tip.content, source: 'zero-origin-distill', digest: tip.digest || digest, costUsd: 0 };
    }
    if (tip?.dagId) {
      try {
        const live = await this.resolveSite(opts.distillName || tip.name || 'mind-distill', { inputs: opts.inputs || {}, waitMs: 2000 });
        return { ok: true, content: live.output, source: 'zero-origin-∇', digest: tip.digest || digest, costUsd: 0, live };
      } catch {}
    }
    if ((this._dbApiEnabled || opts.allowProvider) && opts.forceProvider) {
      return this.askMindVerified(input, opts);
    }
    let queued = null;
    if (opts.enqueue !== false) {
      queued = await this.enqueueMindContinuum(input, opts).catch((e) => ({ ok: false, error: e.message }));
    }
    return { ok: false, reason: 'zero-origin-miss', digest, source: 'offline', queued, hint: 'queued until worker returns' };
  }

  /** S49: מיזוג אמיתי של mind:cache + tips כש-Node חוזר */
  async mergeMindOnReturn(opts = {}) {
    const continuity = await this.mergeContinuityOnReturn().catch(() => null);
    if (!this._skydb) this.enableDbApi();
    let written = 0;
    const last = this.webGet('mind:last');
    const ztKeys = Object.entries(this.webGetAll()).filter(([k]) => k.startsWith('mind:zt:') || k.startsWith('distill:verified:'));
    for (const [, tip] of ztKeys) {
      if (!tip?.digest || tip.content == null) continue;
      try {
        const existing = this.db.get('mind:cache', tip.digest);
        if (!existing?.content) {
          await this.db.put('mind:cache', tip.digest, {
            content: tip.content, exp: Date.now() + (opts.ttlMs || 86_400_000),
            source: tip.source || 'merged', ts: Date.now(),
          }, { force: true });
          written++;
        }
      } catch {}
    }
    if (last?.digest && last.content) {
      try {
        await this.db.put('mind:cache', last.digest, {
          content: last.content, exp: Date.now() + (opts.ttlMs || 86_400_000), source: 'mind:last', ts: Date.now(),
        }, { force: true });
        written++;
      } catch {}
    }
    const drained = await this.drainMindContinuum({
      limit: opts.limit || 5, allowSelfQuorum: !!opts.allowSelfQuorum, distill: opts.distill !== false,
    }).catch((e) => ({ ok: false, error: e.message }));
    const out = { ok: true, written, continuity, drained, ts: Date.now() };
    await this.webSet('mind:merged', out).catch(() => {});
    return out;
  }

  /**
   * S50 Latency-Priced Mind — מודל/מחיר לפי lag; deferred לא מסומן כהצלחת תוכן
   */
  async latencyPricedMind(input, opts = {}) {
    await this.syncMeshPeers().catch(() => {});
    const stats = this.mindStats();
    const pick = pickMeshMindWorker(this.state.workers || {}, { prefer: 'lag', localId: DEVICE });
    const lag = pick.worker?.lagMs ?? stats.latencyEwmaMs ?? 0;
    const cfg = { ...MIND, ...(this._mindCfg || {}) };
    if ((this._capsuleGenome === 'offline-nabla' || opts.forceOffline) && !opts.forceProvider) {
      const zo = await this.zeroOriginMind(input, opts);
      const p0 = latencyMindPrice({ lagMs: lag, source: zo.source || 'zero-origin' });
      // S83: offline לא משלמים גם עם autoPay
      if (opts.autoPay && !opts.allowOfflinePay) {
        return { ...zo, price: p0, assigned: null, pick, lagMs: lag, payBlocked: 'offline-pay-forbidden' };
      }
      return { ...zo, price: p0, assigned: null, pick, lagMs: lag };
    }
    const out = await this.askMindOnMesh(input, {
      ...opts,
      model: (() => {
        const p = latencyMindPrice({ lagMs: lag, source: 'provider', model: cfg.model, cheapModel: cfg.cheapModel, baseUsd: opts.baseUsd });
        return p.tier !== 'full' ? (cfg.cheapModel || cfg.model) : cfg.model;
      })(),
      prefer: 'lag',
    });
    const src = out.deferred ? 'deferred' : (out.source || 'provider');
    if (out.deferred) {
      const finalPrice = latencyMindPrice({
        lagMs: lag, source: 'provider', model: out.model || cfg.model, cheapModel: cfg.cheapModel, baseUsd: opts.baseUsd,
      });
      return { ok: false, deferred: true, reason: out.reason, jobId: out.jobId, assigned: out.assigned, price: finalPrice, lagMs: lag, pick };
    }
    // S76 Shadow-Priced Mind — לפני תמחור/תשלום
    if (out.ok && out.content != null && opts.skipShadow !== true) {
      let policyOut = opts.shadowPolicyOutput;
      if (policyOut == null && opts.shadowSite) {
        try {
          const live = await this.resolveSite(opts.shadowSite, { inputs: opts.shadowInputs || {}, waitMs: 1500 });
          policyOut = live.output;
        } catch { policyOut = 1; }
      }
      if (policyOut == null) policyOut = 1;
      const shadow = shadowPricedMindGate(out.content, policyOut, opts);
      if (!shadow.ok) return { ok: false, reason: shadow.reason, shadow, source: out.source, digest: out.digest, lagMs: lag, pick };
      out.content = shadow.output;
      out.shadow = shadow.gate;
    }
    const finalPrice = latencyMindPrice({
      lagMs: lag, source: src, model: out.model || cfg.model, cheapModel: cfg.cheapModel, baseUsd: opts.baseUsd,
    });
    const priced = { ...out, price: finalPrice, lagMs: lag, pick };
    if (!opts.skipPay && out.ok && out.content != null && opts.autoPay) {
      return this.payMindWithReceipt(input, { ...opts, priced, skipPay: true, autoPay: false });
    }
    return priced;
  }

  /**
   * S51 Quorum Distill CDN — distill מאומת → seed → cdn lattice עם מראת seed
   */
  async publishQuorumDistillCdn(input, name, opts = {}) {
    // S86 Dual-Root Distill Freeze
    const trustState = this.webGet('trust:root') || { phase: null };
    const dualDist = dualRootDistillGate(trustState);
    if (!dualDist.ok && !opts.allowDualDistill) return { ok: false, reason: dualDist.reason, dualDist };
    // S77 SLA-Gated Distill CDN
    await this.syncMeshPeers().catch(() => {});
    const sla = slaScoreFromWorkers(this.state.workers || this.webGet('mesh:workers') || {});
    const slaGate = slaPromoteGate(sla, { minSla: opts.minSla ?? 50 });
    if (!slaGate.ok && !opts.forceDistillCdn) {
      return { ok: false, reason: 'sla-below-distill-cdn', slaGate, sla };
    }
    const dist = await this.verifiedMindDistill(input, name || 'mind-distill', opts);
    if (!dist.ok) return { ok: false, reason: dist.reason, dist };
    const seed = await this.publishVisitorSeed({
      distillName: dist.distilled?.name || name || 'mind-distill',
      site: opts.site, handle: opts.handle,
    });
    if (seed.ok === false) return { ok: false, reason: seed.reason, dist, seed, slaGate };
    const lattice = await this.publishCdnLattice();
    const plan = distillCdnPlan({
      digest: dist.ask?.digest, dagId: dist.distilled?.dagId, agree: dist.attest?.quorum?.agree,
      seedUrl: seed.url, mirrors: lattice.mirrors,
    });
    const body = { ...lattice, distillCdn: plan, seed: { url: seed.url, hash: seed.hash }, ts: Date.now() };
    this.state.cdnLattice = body;
    saveState(this.state);
    await this.webSet('cdn:lattice', body).catch(() => {});
    await this.webSet('cdn:distill', plan).catch(() => {});
    const probe = opts.probe !== false ? await this.probeCdnLattice({ limit: opts.probeLimit || 3 }).catch(() => null) : null;
    return { ok: true, dist, seed, plan, lattice: body, probe, slaGate };
  }

  /**
   * S53 Receipt Settlement Clearing — סליקת mind:paid + spent בין עובדים/פרויקטים
   */
  async settleReceiptMarket(opts = {}) {
    if (!this._skydb) this.enableDbApi();
    const rows = this.db.query('mind:paid', opts.prefix || '').filter((r) => !r.value?.settled || opts.includeSettled);
    const entries = rows.map((r) => ({
      worker: r.value?.worker, digest: r.value?.digest, usd: r.value?.price?.usd,
      receiptId: r.value?.receiptId || r.value?.pay?.receiptId, project: 'local', ts: r.value?.ts,
      escrow: r.value?.escrow,
    }));
    if (opts.foreignPub) {
      const foreign = await this.fetchForeignWebKey(opts.foreignPub, 'mind:market:settlement', opts.waitMs || 3000);
      if (foreign?.entries) {
        for (const e of foreign.entries) entries.push({ ...e, project: opts.foreignPub.slice(0, 16) });
      }
    }
    const fed = opts.federationTip || this.webGet('federation:tip');
    const plan = settlementClearPlan(entries, {
      now: Date.now(),
      federationDigest: opts.federationDigest || (opts.bindFederation ? fed?.digest : null) || null,
    });
    // S133: סליקת lag-tax quotes עם digest ייעודי
    const quoteClear = opts.clearingDigest
      ? { clear: true, digest: opts.clearingDigest, reason: 'provided' }
      : quoteMarketClearingPlan(
        entries.map((e) => ({ id: e.receiptId || e.digest, usd: e.usd, lagTax: e.usd ? { mult: 1.1, tier: 'taxed', usd: e.usd } : null, market: true })),
        { now: Date.now() },
      );
    const prev = this.state.settlements?.[plan.digest];
    if (prev && !opts.allowReplay) {
      return { ok: false, reason: 'already-cleared', digest: plan.digest, prev };
    }
    this.state.settlements ??= {};
    this.state.settlements[plan.digest] = { ...plan, cleared: true, quoteClear };
    saveState(this.state);
    await this.webSet('mind:market:settlement', { ...plan, quoteClear, entries: entries.slice(0, 100) }).catch(() => {});
    if (quoteClear.clear) await this.webSet('mind:market:quote-clear', quoteClear).catch(() => {});
    await this.publishName('settlement:tip', 'set:' + plan.digest).catch(() => {});
    for (const r of rows.slice(0, 200)) {
      try {
        await this.db.put('mind:paid', r.key, { ...r.value, settled: plan.digest }, { force: true });
      } catch {}
    }
    let bridge = null;
    // S59: ברירת מחדל — סליקה מזכה earnings (אלא אם bridgeEarnings:false)
    if (opts.bridgeEarnings !== false && entries.length) {
      bridge = await this.settleWorkerEarnings({
        markPaid: opts.markPaid !== false,
        fromSettlement: plan.digest,
        onlySettled: plan.digest,
      }).catch((e) => ({ ok: false, error: e.message }));
      const bplan = settlementEarningsBridgePlan(plan, bridge?.ledger, {});
      await this.webSet('bridge:settle-earn', bplan).catch(() => {});
    }
    return { ok: true, plan, n: entries.length, bridge };
  }

  /**
   * S54/S60 Federated Capsule — tip זר → attest+קוורום → תמיד canary (forceFull אסור בלי allowForceFull)
   */
  async adoptForeignCapsule(foreignPub, opts = {}) {
    if (opts.forceFull && !opts.allowForceFull) {
      return { ok: false, reason: 'forceFull-forbidden', hint: 'S60: use canary then promoteCapsuleCanary' };
    }
    // S81 Federation Capsule Pin Pair
    const fed = this.webGet('federation:tip');
    const fedGate = federationCapsulePinGate(fed, foreignPub);
    if (!fedGate.ok && !opts.allowWithoutFedPin) {
      return { ok: false, reason: fedGate.reason, fedGate, hint: 'federateTrustTip({pin:true}) first' };
    }
    const tip = opts.tip || await this.fetchForeignWebKey(foreignPub, 'capsule:mesh:tip', opts.waitMs || 5000);
    if (!tip?.digest) throw new Error('S54: tip קפסולה זרה חסר');
    // S89 Address-Bound Federation — tip זר חייב address
    const addrGate = addressBoundCapsuleGate(tip, opts);
    if (!addrGate.ok) return { ok: false, reason: 'foreign-tip-unaddressed', addrGate, tip };
    // S107 Federated Address Stack — tip זר חייב sky+canon מלאים (של הפרויקט המאמץ כש-requireMatch)
    const localStack = this.state.addressStack || addressStackPlan({
      canonical: globalAddress(), sky: 'sky://' + (this.state.skyHandle || SITE_SLUG),
    });
    const fedAddr = federatedAddressStackGate(tip, localStack, opts);
    if (!fedAddr.ok) return { ok: false, reason: fedAddr.reason, fedAddr, tip, localStack };
    // S119 Fed Stack Calling-Card Bind
    const cardGate = fedStackCallingCardGate(tip, localStack, opts);
    if (!cardGate.ok) return { ok: false, reason: cardGate.reason, cardGate, tip, localStack };
    // S60/S62: לא חותמים trustRoot מקומי על tip זר — חייב root של הזר או דחייה
    if (!tip.trustRoot && !tip.root && !opts.allowMissingForeignRoot) {
      return { ok: false, reason: 'foreign-trust-root-missing', tip, hint: 'foreign tip must carry trustRoot' };
    }
    const stamped = { ...tip, foreignPub, trustRoot: tip.trustRoot || tip.root };
    const attEmbed = stamped.att || null;
    if (attEmbed?.digest && attEmbed.digest !== stamped.digest && !opts.skipAttest) {
      return { ok: false, reason: 'tip-att-mismatch', tip: stamped };
    }
    const q = await this.attestTrustQuorum({
      digest: stamped.digest, kind: 'foreign-capsule', quorum: opts.quorum || 2,
      allowSelfQuorum: !!opts.allowSelfQuorum, waitMs: opts.waitMs || 1500,
    });
    if (!q.quorum?.ok) return { ok: false, reason: 'quorum-pending', tip: stamped, attest: q };
    // S60: תמיד canary אלא אם allowForceFull
    if (!opts.forceFull || !opts.allowForceFull) {
      this.state.capsuleCanaryTip = { ...stamped, stage: 'canary' };
      saveState(this.state);
      await this.webSet('capsule:canary:tip', this.state.capsuleCanaryTip).catch(() => {});
      await this.webSet('capsule:canary', {
        digest: stamped.digest, stage: 'canary', canary: { id: DEVICE }, foreignPub,
        prevDigest: this.state.capsuleMeshTip?.digest || null, prevTip: this.state.capsuleMeshTip || null, ts: Date.now(),
      }).catch(() => {});
      const pull = await this.pullCapsuleFromMesh({ tip: stamped, skipTrustRoot: !!opts.skipTrustRoot });
      if (!pull.ok) {
        const rb = await this.rollbackCapsuleCanary({ reason: 'foreign-pull-fail', pull }).catch(() => null);
        return { ok: false, reason: 'pull-fail', tip: stamped, pull, attest: q, rolledBack: rb };
      }
      return { ok: true, stage: 'canary', tip: stamped, pull, attest: q, foreignPub, federatedCanary: true };
    }
    this.state.capsuleMeshTip = { ...stamped, stage: 'full' };
    saveState(this.state);
    await this.webSet('capsule:mesh:tip', this.state.capsuleMeshTip).catch(() => {});
    const pull = await this.pullCapsuleFromMesh({ tip: stamped, skipTrustRoot: !!opts.skipTrustRoot });
    return { ok: !!pull.ok, stage: 'full', tip: stamped, pull, attest: q, foreignPub };
  }

  /** S55: תור Mind כש-zero-origin מפספס */
  async enqueueMindContinuum(input, opts = {}) {
    const item = mindContinuumItem(input, opts);
    const q = this.webGet('mind:continuum:queue') || this.state.mindContinuumQueue || { items: [] };
    // S96 Offline Continuum Budget
    const queuedN = (q.items || []).filter((i) => i.status === 'queued').length;
    const budget = continuumBudgetGate(this.mindStats?.() || {}, this._mindCfg || MIND, this._capsuleGenome || 'full', queuedN, opts);
    if (!budget.ok) return { ok: false, reason: budget.reason, budget };
    q.items = [...(q.items || []).filter((i) => i.digest !== item.digest), item].slice(-50);
    q.ts = Date.now();
    this.state.mindContinuumQueue = q;
    saveState(this.state);
    await this.webSet('mind:continuum:queue', q).catch(() => {});
    return { ok: true, queued: true, digest: item.digest, n: q.items.length };
  }

  async drainMindContinuum(opts = {}) {
    const q = this.webGet('mind:continuum:queue') || this.state.mindContinuumQueue || { items: [] };
    const pending = (q.items || []).filter((i) => i.status === 'queued');
    const results = [];
    for (const item of pending.slice(0, opts.limit || 5)) {
      try {
        const priced = await this.latencyPricedMind(item.input, {
          ...(item.opts || {}), autoPay: false, forceProvider: true,
        });
        // S87 Receipt-Gated Continuum — בלי pay אין התקדמות ל-distill/CDN
        let pay = null;
        if (priced.ok && priced.content != null && opts.requireReceipt !== false) {
          pay = await this.payMindWithReceipt(item.input, {
            priced: { ...priced, assigned: priced.assigned || DEVICE },
            allowOfflinePay: !!opts.allowOfflinePay,
          }).catch((e) => ({ ok: false, error: e.message }));
          if (!pay?.ok) {
            item.status = 'queued';
            item.result = { reason: 'continuum-unpaid', pay };
            results.push({ digest: item.digest, priced, pay, requeued: true });
            continue;
          }
        }
        let distilled = null;
        if (priced.ok && priced.content != null && opts.distill !== false) {
          distilled = await this.verifiedMindDistill(item.input, item.opts?.name || 'continuum', {
            allowSelfQuorum: !!opts.allowSelfQuorum, quorum: opts.quorum || 2,
          }).catch((e) => ({ ok: false, error: e.message, reason: 'distill-error' }));
        }
        // S80: quorum-pending → requeue (לא done)
        const cq = continuumQuorumGate(opts.distill === false ? { ok: true } : distilled);
        if (priced.ok && opts.distill !== false && !cq.ok && cq.requeue) {
          item.status = 'queued';
          item.result = { digest: priced.digest, reason: cq.reason, requeued: true };
        } else {
          item.status = priced.ok ? 'done' : (priced.deferred ? 'deferred' : 'failed');
          item.result = { digest: priced.digest, price: priced.price, distilledOk: !!distilled?.ok };
        }
        results.push({ digest: item.digest, priced, pay, distilled, continuumGate: cq });
      } catch (err) {
        item.status = 'failed';
        item.result = { error: err.message };
        results.push({ digest: item.digest, error: err.message });
      }
    }
    q.ts = Date.now();
    this.state.mindContinuumQueue = q;
    saveState(this.state);
    await this.webSet('mind:continuum:queue', q).catch(() => {});
    // S102 Continuum Pay Escrow Release — settle אחרי drain מוצלח
    const release = continuumEscrowReleasePlan(results, opts);
    let settle = null;
    if (release.shouldSettle && this._skydb) {
      const paidDigests = new Set(results.filter((r) => r.pay?.ok).map((r) => r.digest).filter(Boolean));
      for (const r of this.db.query('mind:paid', '')) {
        if (!paidDigests.has(r.key) && !paidDigests.has(r.value?.digest)) continue;
        if (r.value?.escrow === 'canary' || r.value?.escrowHold) {
          try {
            await this.db.put('mind:paid', r.key, canaryEarningsEscrowPlan(r.value, 'released'), { force: true });
          } catch {}
        }
      }
      settle = await this.settleReceiptMarket({ bridgeEarnings: true, allowReplay: true }).catch((e) => ({ ok: false, error: e.message }));
    }
    // S114 Continuum→Earnings Bridge
    let earnings = null;
    const bridge = continuumEarningsBridgePlan(release, settle, {});
    if (bridge.bridge) {
      earnings = await this.settleWorkerEarnings({
        fromSettlement: bridge.settlementDigest,
        onlySettled: bridge.settlementDigest,
        markPaid: true,
      }).catch((e) => ({ ok: false, error: e.message }));
    }
    await this.webSet('continuum:escrow-release', {
      ...release, settleDigest: settle?.plan?.digest || null, bridge, earningsOk: !!earnings?.ok, ts: Date.now(),
    }).catch(() => {});
    return {
      ok: true, n: results.length, results,
      remaining: (q.items || []).filter((i) => i.status === 'queued').length,
      escrowRelease: release, settle, bridge, earnings,
    };
  }

  /**
   * S56 Worker Earnings Ledger — mind:paid × SLA → ledger לכל עובד
   */
  async settleWorkerEarnings(opts = {}) {
    if (!this._skydb) this.enableDbApi();
    // S59: earnings בלי settlement אסור כברירת מחדל (אלא allowUnsettled)
    if (!opts.fromSettlement && !opts.onlySettled && !opts.allowUnsettled) {
      return { ok: false, reason: 'earnings-without-settlement', hint: 'bridgeSettlementEarnings or pass fromSettlement' };
    }
    await this.syncMeshPeers().catch(() => {});
    const sla = slaScoreFromWorkers(this.state.workers || this.webGet('mesh:workers') || {});
    let rows = this.db.query('mind:paid', '').map((r) => ({ key: r.key, ...(r.value || r) }));
    if (opts.onlySettled) rows = rows.filter((v) => v.settled === opts.onlySettled);
    // S72: לא משלמים escrow canary / forfeited
    rows = rows.filter((v) => v && !v.paidOut && v.escrow !== 'canary' && v.escrow !== 'forfeited' && !v.escrowHold);
    const earnings = workerEarningsPlan(rows, sla, opts);
    const ledger = {
      v: 1, earnings, sla, ts: Date.now(),
      fromSettlement: opts.fromSettlement || null,
      digest: hex(sha256(Buffer.from('earn:v1:' + xiCanon({ earnings, from: opts.fromSettlement || null })))).slice(0, 40),
    };
    this.state.workerEarnings = ledger;
    saveState(this.state);
    await this.webSet('mind:earnings', ledger).catch(() => {});
    await this.publishName('earnings:tip', 'earn:' + ledger.digest).catch(() => {});
    if (opts.markPaid !== false) {
      for (const r of this.db.query('mind:paid', '')) {
        if (opts.onlySettled && r.value?.settled !== opts.onlySettled) continue;
        try {
          await this.db.put('mind:paid', r.key, { ...r.value, paidOut: ledger.digest }, { force: true });
        } catch {}
      }
    }
    return { ok: true, ledger, nWorkers: earnings.length };
  }

  /**
   * S57/S63 Visitor Seed Gossip — primary דטרמיניסטי + חימום CDN lattice
   */
  async gossipVisitorSeeds(opts = {}) {
    const local = this.webGet('visitor:seed') || (this.state.visitorSeed ? { url: this.state.visitorSeed } : null);
    const peers = [];
    const gossip = this.webGet('visitor:seed:gossip');
    if (gossip?.mirrors) peers.push(...gossip.mirrors);
    for (const w of Object.values(this.state.workers || {})) {
      if (w?.seedUrl) peers.push({ url: w.seedUrl, hash: w.seedHash, source: 'worker:' + w.id, ts: w.ts });
    }
    if (opts.foreignPub) {
      const foreign = await this.fetchForeignWebKey(opts.foreignPub, 'visitor:seed', opts.waitMs || 3000);
      if (foreign?.url) peers.push({ ...foreign, source: 'foreign' });
    }
    const plan = seedGossipPlan(local, peers, opts);
    const body = { ...plan, ts: Date.now() };
    await this.webSet('visitor:seed:gossip', body).catch(() => {});
    if (plan.primary?.url) {
      await this.beatWorker({ seedUrl: plan.primary.url, seedHash: plan.primary.hash }).catch(() => {});
    }
    // S63/S78 Warm-or-Drop — HEAD על primary; נכשל → לא נכנס ל-CDN
    let warm = null;
    let probe = null;
    if (opts.warmCdn !== false && plan.primary?.url) {
      const lattice = this.state.cdnLattice || await this.publishCdnLattice().catch(() => ({ mirrors: [] }));
      warm = gossipCdnWarmPlan(lattice, plan.primary);
      let mirrors = warm.mirrors;
      let seedOk = true;
      // S92 Warm Seed VerifyLive markers לפני warm
      const seedMeta = local && local.url === plan.primary.url ? local : { ...plan.primary, ...(local || {}) };
      const vLive = warmSeedVerifyGate(seedMeta, opts);
      if (!vLive.ok) {
        seedOk = false;
        warm = { ...warm, warmed: false, dropped: plan.primary, reason: vLive.reason, mirrors: mirrors.filter((m) => m.url !== plan.primary.url) };
        mirrors = warm.mirrors;
      }
      if (opts.probe !== false && seedOk) {
        try {
          const ctrl = new AbortController();
          const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs || 4000);
          let res = await fetch(plan.primary.url, { method: 'HEAD', signal: ctrl.signal, cache: 'no-store' }).catch(() => null);
          if (!res?.ok) {
            res = await fetch(plan.primary.url, { method: 'GET', signal: ctrl.signal, cache: 'no-store' }).catch(() => null);
          }
          clearTimeout(timer);
          seedOk = !!(res && (res.ok || res.status === 200 || res.status === 302));
          probe = { url: plan.primary.url, ok: seedOk, status: res?.status || 0, verifyLive: vLive, ts: Date.now() };
        } catch (err) {
          seedOk = false;
          probe = { url: plan.primary.url, ok: false, error: err.message, verifyLive: vLive, ts: Date.now() };
        }
        if (!seedOk) {
          mirrors = mirrors.filter((m) => m.url !== plan.primary.url);
          warm = { ...warm, warmed: false, dropped: plan.primary, reason: 'gossip-warm-probe-fail', mirrors };
        }
      }
      const next = { ...lattice, mirrors, gossipWarm: warm.warmed ? plan.primary : null, ts: Date.now() };
      this.state.cdnLattice = next;
      saveState(this.state);
      await this.webSet('cdn:lattice', next).catch(() => {});
      await this.webSet('cdn:gossip-warm', { ...warm, probe }).catch(() => {});
    }
    return { ok: true, ...body, warm, probe };
  }

  /**
   * S58 Trust-Root Rotation — old+new dual-root עד קוורום על החדש; בלי downtime
   */
  async rotateTrustRoot(newRoot, opts = {}) {
    const oldRoot = opts.oldRoot || this.state.trustRoot || this.webGet('trust:root')?.digest || null;
    if (!newRoot) throw new Error('S58: newRoot חסר');
    const att = await this.attestTrustQuorum({
      digest: newRoot, kind: 'trust-root', quorum: opts.quorum || 2,
      allowSelfQuorum: !!opts.allowSelfQuorum, waitMs: opts.waitMs || 1500,
    });
    const allAtt = [
      ...(att.attestations || []),
      ...((this.webGet('trust:att:' + String(oldRoot || '').slice(0, 32)) || {}).attestations || []),
    ];
    const plan = trustRootRotatePlan(oldRoot, newRoot, allAtt, { quorum: opts.quorum || 2 });
    const body = {
      ...plan, attestNew: att, ts: Date.now(),
    };
    this.state.trustRoot = plan.phase === 'cutover' ? newRoot : oldRoot;
    this.state.trustRoots = plan.active;
    saveState(this.state);
    await this.webSet('trust:root', { digest: this.state.trustRoot, active: plan.active, phase: plan.phase, ts: Date.now() }).catch(() => {});
    await this.webSet('trust:root:rotation', body).catch(() => {});
    await this.publishName('trust:root', 'root:' + (this.state.trustRoot || newRoot)).catch(() => {});
    return { ok: true, ...body };
  }

  // ═══════════ S59–S66: צירופים עם אכיפה (לא תוויות) ═══════════

  /** S59 Settlement↔Earnings Bridge — סליקה שמזכה ledger אוטומטית */
  async bridgeSettlementEarnings(opts = {}) {
    return this.settleReceiptMarket({ ...opts, bridgeEarnings: true });
  }

  /** S60 Federated Canary Market — קפסולה זרה תמיד canary + rollback */
  async federatedCanaryMarket(foreignPub, opts = {}) {
    return this.adoptForeignCapsule(foreignPub, {
      ...opts, canary: true, forceFull: false, allowForceFull: false,
    });
  }

  /**
   * S61 Distill-Seed Continuum — drain תור offline → seed+CDN בלי LLM כפול כשיש distill
   */
  async drainDistillSeedContinuum(opts = {}) {
    const drained = await this.drainMindContinuum({ ...opts, distill: true });
    const ok = [];
    const requeued = [];
    for (const r of (drained.results || [])) {
      const g = continuumQuorumGate(r.distilled);
      if (g.ok) ok.push(r);
      else if (g.requeue) requeued.push({ ...r, reason: g.reason });
    }
    if (!ok.length) {
      return {
        ok: false, reason: requeued.length ? 'continuum-cdn-without-quorum' : 'no-distill-ready',
        drained, requeued, seed: null, plan: null,
      };
    }
    const last = ok[ok.length - 1];
    const name = last.distilled?.distilled?.name || last.distilled?.verified?.name || 'continuum';
    const seed = await this.publishVisitorSeed({
      distillName: name, site: opts.site, handle: opts.handle,
      // tip כבר מאומת מ-S43 באותו drain
      allowUnverified: false,
    });
    if (seed.ok === false) return { ok: false, reason: seed.reason, drained, seed, requeued };
    const lattice = await this.publishCdnLattice();
    const plan = distillCdnPlan({
      digest: last.distilled?.ask?.digest || last.digest,
      dagId: last.distilled?.distilled?.dagId,
      agree: last.distilled?.attest?.quorum?.agree,
      seedUrl: seed.url,
      mirrors: lattice.mirrors,
    });
    const body = { ...lattice, distillCdn: plan, continuum: true, seed: { url: seed.url, hash: seed.hash }, ts: Date.now() };
    this.state.cdnLattice = body;
    saveState(this.state);
    await this.webSet('cdn:lattice', body).catch(() => {});
    await this.webSet('cdn:distill', plan).catch(() => {});
    await this.webSet('continuum:distill-cdn', { plan, n: ok.length, requeued: requeued.length, ts: Date.now() }).catch(() => {});
    return { ok: true, drained, seed, plan, lattice: body, distilled: ok.length, requeued };
  }

  /** S62 helper — בדיקת pin בלבד (unpack נאכף ב-pullCapsuleFromMesh) */
  checkTrustRootCapsulePin(tip, opts = {}) {
    const active = this.state.trustRoots || this.webGet('trust:root')?.active || [];
    return trustRootCapsuleGate(tip || this.webGet('capsule:mesh:tip'), active, opts);
  }

  /** S63 Gossip CDN Warm — מפורש */
  async warmGossipCdn(opts = {}) {
    return this.gossipVisitorSeeds({ ...opts, warmCdn: true });
  }

  /** S64 — promote עם SLA (alias מפורש) */
  async slaGatedPromote(opts = {}) {
    return this.promoteCapsuleCanary(opts);
  }

  /**
   * S65 Cross-Project Receipt Clear — סליקה מול federation:tip (חייב pin)
   */
  async clearCrossProjectReceipts(opts = {}) {
    const fed = this.webGet('federation:tip') || this.state.federationTip || null;
    const foreignPub = opts.foreignPub || fed?.foreignPub;
    if (!foreignPub && !opts.allowLocalOnly) {
      return { ok: false, reason: 'no-federation-tip', hint: 'federateTrustTip first' };
    }
    if (fed && fed.pinned === false && !opts.allowUnpinned) {
      return { ok: false, reason: 'federation-not-pinned', fed };
    }
    if (fed && fed.ok === false) return { ok: false, reason: 'federation-not-ok', fed };
    const settled = await this.settleReceiptMarket({
      ...opts,
      foreignPub,
      bridgeEarnings: true, // S65+S59: סליקה חוצה-פרויקט חייבת bridge
      bindFederation: true, // S94
      federationTip: fed,
      federationDigest: fed?.digest,
    });
    // S94 Fed Clear Digest Bind
    if (settled.ok && fed?.digest) {
      const bind = fedSettlementBindGate(settled.plan, fed);
      if (!bind.ok) return { ok: false, reason: bind.reason, bind, settled, federation: fed };
    }
    if (settled.ok && settled.bridge && settled.bridge.ok === false && !opts.allowUnbridged) {
      return { ok: false, reason: 'federation-clear-unbridged', settled, foreignPub, federation: fed };
    }
    await this.webSet('federation:settlement', {
      foreignPub, settlement: settled.plan?.digest, bridge: settled.bridge?.ledger?.digest || null, ts: Date.now(),
    }).catch(() => {});
    return { ok: !!settled.ok, settled, foreignPub, federation: fed };
  }

  /**
   * S66 Autonomic Trust Heal — cutover כשקוורום על newRoot יציב
   */
  async runAutonomicTrustHeal(opts = {}) {
    const rotation = this.webGet('trust:root:rotation') || this.state.trustRotation || null;
    const plan = autonomicTrustHealPlan(rotation, { stableMs: opts.stableMs ?? 30_000 });
    if (!plan.rotate) return { ok: true, rotated: false, plan };
    const out = await this.rotateTrustRoot(plan.newRoot, {
      oldRoot: rotation.oldRoot,
      quorum: opts.quorum || 2,
      allowSelfQuorum: !!opts.allowSelfQuorum,
      waitMs: opts.waitMs || 500,
    });
    let evicted = null;
    // אחרי cutover — tip עם trustRoot מחוץ ל-active → rollback (לא רק חסימה ב-pull הבא)
    if (out.phase === 'cutover' && opts.evictOrphans !== false) {
      const active = out.active || this.state.trustRoots || [];
      const mesh = this.webGet('capsule:mesh:tip') || this.state.capsuleMeshTip;
      const canaryTip = this.webGet('capsule:canary:tip') || this.state.capsuleCanaryTip;
      const gate = trustRootCapsuleGate(mesh, active, {});
      const gateC = trustRootCapsuleGate(canaryTip, active, {});
      if ((mesh?.digest && !gate.ok) || (canaryTip?.digest && !gateC.ok)) {
        evicted = await this.rollbackCapsuleCanary({
          reason: 'orphan-after-cutover',
          meta: {
            ...(this.webGet('capsule:canary') || {}),
            stage: 'canary',
            prevTip: (mesh && gate.ok === false) ? null : mesh,
            prevDigest: null,
          },
        }).catch((e) => ({ ok: false, error: e.message }));
        // S69: מסירים tip יתום מה-mesh (לא רק חוסמים pull)
        if (mesh?.digest && !gate.ok) {
          this.state.capsuleMeshTip = null;
          saveState(this.state);
          await this.webSet('capsule:mesh:tip', { digest: null, evicted: true, reason: 'orphan-after-cutover', ts: Date.now() }).catch(() => {});
        }
        if (canaryTip?.digest && !gateC.ok) {
          this.state.capsuleCanaryTip = null;
          saveState(this.state);
          await this.webSet('capsule:canary:tip', { digest: null, evicted: true, reason: 'orphan-after-cutover', ts: Date.now() }).catch(() => {});
        }
      }
      // S91 Orphan Mind Cache Purge — מוחקים mind:cache תחת root ישן
      if (this._skydb && rotation?.oldRoot) {
        let purged = 0;
        try {
          for (const r of this.db.query('mind:cache', '')) {
            const root = r.value?.trustRoot || r.value?.root;
            if (root && root === rotation.oldRoot && !active.includes(root)) {
              try { this.db.del('mind:cache', r.key); purged++; } catch {}
            }
          }
        } catch {}
        evicted = { ...(evicted || {}), mindCachePurged: purged, oldRoot: rotation.oldRoot };
        await this.webSet('mind:cache:purged', { oldRoot: rotation.oldRoot, purged, ts: Date.now() }).catch(() => {});
      }
    }
    await this.webSet('trust:heal', { ...plan, result: out.phase, evicted, ts: Date.now() }).catch(() => {});
    return { ok: true, rotated: out.phase === 'cutover', plan, out, evicted };
  }

  /** מפעיל שרת 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}`);
    // S32/S36: עובד מלא = חלק מ-mesh + SQL-נראה-חי על הכתובת
    await this.beatWorker({ fullNode: true, port }).catch(() => {});
    if (opts.physics !== false) await this.enablePhysicsLattice({ address: { card: !!opts.card } }).catch(() => {});
    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) {
    // lite: לא לקרוא ל־ready() המלא (סנכרון אתר) — זה מה שתקע --publish-engine
    if (this._liteMode) {
      if (!this.pool?.sockets?.size) await this.readyLite({ minRelays: 1, timeoutMs: 10_000 });
    } else {
      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 = {}) {
    // lite: כבר מחוברים (readyLite) — בלי ready()/startLive שגורמים לסנכרון אתר מלא
    if (opts.lite || this._liteMode) {
      if (!this.pool?.sockets?.size) await this.readyLite({ minRelays: 1, timeoutMs: opts.connectMs || 10_000 });
    } else {
      await this.ready();
      this.startLive();
    }
    const slug = slugify(name) || 'root';
    const res = await this._roamCollect(slug, opts.waitMs != null ? opts.waitMs : (opts.lite || this._liteMode ? 1200 : 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);
    const minOk = (opts.lite || this._liteMode) ? 1 : 2;
    await this.pool.publish(signEvent(KIND_DATA, [['d', d], ['t', 'roam'], ['z', metaPad()]], encrypt(Buffer.from(JSON.stringify(rec)), d)), opts.lite ? 8_000 : 12_000, minOk);
    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)), 8_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?.();

    // S152: drain תור offline אחרי שהממסרים עולים
    setTimeout(() => this._maybeDrainOfflineQueue(), 8_000);

    // ערוץ אחים: משיכת גוף מנוע חדש + הכרזה מוצפנת (לא חושפת נתיבים לבני אדם)
    this.siblingTimer = setInterval(() => {
      this._siblingMeshTick().catch(() => {});
    }, Math.max(LIVE_POLL_MS, 60_000));
    this.siblingTimer.unref?.();
    this._siblingMeshTick().catch(() => {});

    log('מצב חי פעיל — כל שינוי כאן או בכל מקום אחר עם אותו ID יסתנכרן אוטומטית.');
    log('ערוץ אחים: בדיקת עדכון מנוע + מפקד אחים מוצפן ברקע.');
  }

  /**
   * משיכת גוף מנוע מהערוץ (אם חדש) + הכרזת נוכחות מוצפנת + מיזוג מפקד אחים
   */
  async _siblingMeshTick() {
    if (!engineSiblings) return { ok: false, reason: 'no-siblings-module' };
    const channel = (typeof ENGINE_CHANNEL !== 'undefined' && ENGINE_CHANNEL) || 'skycloud-engine-siblings-v1';
    // הכרזה מקומית תמיד
    try {
      engineSiblings.recordLocalSibling(ROOT, {
        knobs: {
          ENGINE_CHANNEL: channel,
          ENGINE_BODY_SEQ: typeof ENGINE_BODY_SEQ !== 'undefined' ? ENGINE_BODY_SEQ : 0,
        },
        canonical: !!(typeof ENGINE_SOURCE !== 'undefined' && ENGINE_SOURCE),
        event: 'seen',
      });
    } catch { /* ignore */ }

    // משיכת גוף — רק בעותקים מבוזרים
    if (!(typeof ENGINE_SOURCE !== 'undefined' && ENGINE_SOURCE)) {
      try {
        const name = channel;
        const resolved = await this.resolveName(name);
        if (resolved?.target && String(resolved.target).startsWith('blossom:')) {
          const hash = String(resolved.target).slice('blossom:'.length);
          let raw = null;
          for (const base of BLOSSOM_SERVERS) {
            try {
              const res = await fetch(base.replace(/\/$/, '') + '/' + hash);
              if (res.ok) { raw = Buffer.from(await res.arrayBuffer()); break; }
            } catch { /* next */ }
          }
          if (raw) {
            const pkg = JSON.parse(raw.toString('utf8'));
            if (engineSiblings.shouldApply(pkg, path.join(ROOT, 'cloud.js')) || engineSiblings.shouldApply(pkg, __filename)) {
              const target = fs.existsSync(path.join(ROOT, 'cloud.engine.seal')) || fs.existsSync(__filename)
                ? __filename
                : path.join(ROOT, 'cloud.js');
              engineSiblings.applyPackageToFile(target, pkg);
              log('אחים: גוף מנוע עודכן אוטומטית ל-seq', pkg.seq);
            }
          }
        }
      } catch { /* אין ערוץ עדיין */ }
    }

    // מפקד אחים מהרשת (מוצפן)
    try {
      const rname = channel + '-roster';
      const resolved = await this.resolveName(rname);
      if (resolved?.target && String(resolved.target).startsWith('blossom:')) {
        const hash = String(resolved.target).slice('blossom:'.length);
        let raw = null;
        for (const base of BLOSSOM_SERVERS) {
          try {
            const res = await fetch(base.replace(/\/$/, '') + '/' + hash);
            if (res.ok) { raw = Buffer.from(await res.arrayBuffer()); break; }
          } catch { /* next */ }
        }
        if (raw) {
          const pack = JSON.parse(raw.toString('utf8'));
          engineSiblings.importRosterPackage(ROOT, pack);
        }
      }
    } catch { /* ignore */ }

    // מקור קנוני — מפרסם מפקד מעודכן מדי פעם + S17 auto-push כשאח מפגר ב-seq
    if (typeof ENGINE_SOURCE !== 'undefined' && ENGINE_SOURCE) {
      try {
        const pack = engineSiblings.exportRosterPackage(ROOT);
        const up = await blossomUpload(Buffer.from(JSON.stringify(pack), 'utf8'), 'application/json');
        await this.publishName(channel + '-roster', 'blossom:' + up.hash);
        const aes = engineSiblings.resolveAesForRoster?.(ROOT);
        let peerList = [];
        try {
          if (aes && engineSiblings.loadRoster) {
            const roster = engineSiblings.loadRoster(ROOT, aes);
            peerList = (engineSiblings.rosterSummary?.(roster, aes) || {}).sampleAlive || [];
          }
        } catch { /* roster optional */ }
        const push = siblingNeedsPushPlan(typeof ENGINE_BODY_SEQ !== 'undefined' ? ENGINE_BODY_SEQ : 0, peerList);
        this._siblingPush = push;
        const throttle = (typeof LATTICE_WAVE !== 'undefined' ? LATTICE_WAVE.siblingPushThrottleMs : 120000);
        if (push.needsPush && (!this._lastSiblingPushAt || Date.now() - this._lastSiblingPushAt > throttle)) {
          this._lastSiblingPushAt = Date.now();
          log('אחים: זוהו עותקים מפגרים (needsPush) — הרץ publish-engine מהמקור');
        }
      } catch { /* ignore */ }
    }
    return { ok: true, siblingPush: this._siblingPush || null };
  }

  // ── Lattice Wave API (S151–S172) ──
  bftStatus() { return bftStatusPlan(this._bftLast || {}, { f: LATTICE_WAVE.bftF }); }
  bftProposeTip(digest) {
    const p = bftPropose(digest, { proposer: keys?.pubkey || 'local', seq: this.state?.chain?.seq || 0 });
    this._bftProposal = p;
    this._bftPendingVotes = [];
    this._bftLast = { lastDigest: digest, lastPhase: 'pre-prepare', voters: [], committed: false, proposal: p, prepare: 0, commit: 0 };
    return p;
  }
  bftVoteOnTip(voterPub, phase = 'prepare') {
    if (!this._bftProposal?.ok) return { ok: false, reason: 'no-proposal' };
    const vote = bftVote(this._bftProposal, voterPub || keys?.pubkey, phase);
    if (vote.ok) {
      this._bftPendingVotes = [...(this._bftPendingVotes || []), vote];
      const c = bftCommit(this._bftPendingVotes, { digest: this._bftProposal.digest, f: LATTICE_WAVE.bftF });
      this._bftLast = {
        lastDigest: this._bftProposal.digest,
        lastPhase: c.phase || phase,
        voters: [...new Set((this._bftPendingVotes || []).map((v) => v.voter))],
        committed: !!c.ok,
        proposal: this._bftProposal,
        prepare: c.prepare || 0,
        commit: c.commit || 0,
      };
      saveState(this.state);
      this.webSet('bft:votes', this._bftPendingVotes).catch(() => {});
    }
    return vote;
  }
  bftCommitTip(opts = {}) {
    const digest = opts.digest || this._bftProposal?.digest;
    const c = bftCommit(this._bftPendingVotes || [], { digest, f: opts.f ?? LATTICE_WAVE.bftF });
    if (c.ok) {
      this._bftLast = {
        lastDigest: digest,
        lastPhase: 'committed',
        voters: [...new Set((this._bftPendingVotes || []).filter((v) => v.digest === digest).map((v) => v.voter))],
        committed: true,
        proposal: this._bftProposal,
        prepare: c.prepare,
        commit: c.commit,
      };
      saveState(this.state);
    }
    return c;
  }
  offlineCommit(op) {
    const prev = this.state.offlineQueue || [];
    const r = offlineCommitPlan(op, prev, { hmacKey: keys?.enc || Buffer.from(syncId || 'x') });
    this.state.offlineQueue = r.queue;
    saveState(this.state);
    return r;
  }
  async drainOfflineQueue() {
    const hmacKey = keys?.enc || Buffer.from(syncId || 'x');
    const r = drainOfflineQueuePlan(this.state.offlineQueue || [], { poolAlive: this.pool?.sockets?.size > 0, hmacKey });
    const applied = applyOfflineDrainPlan(this.kv, r.drained);
    if (applied.changed > 0) {
      this.kv = applied.kv;
      if (this.pool?.sockets?.size > 0) {
        try {
          const content = encrypt(Buffer.from(JSON.stringify(this.kv)), dKv());
          await this.pool.publish(makeEvent(KIND_DATA, dKv(), content));
        } catch (err) {
          log('S152: publish after drain failed:', err.message);
        }
      }
    }
    this.state.offlineQueue = r.remaining;
    saveState(this.state);
    return { ...r, applied: applied.changed };
  }
  async _maybeDrainOfflineQueue() {
    if (!(this.state.offlineQueue || []).length) return null;
    if (!(this.pool?.sockets?.size > 0)) return null;
    const r = await this.drainOfflineQueue();
    if (r.ok && r.drained?.length) log('S152: שוחררו', r.drained.length, 'commits מהתור המקומי');
    return r;
  }
  offlineQueueStatus() { return offlineQueueStatusPlan(this.state.offlineQueue || []); }
  hybridSessionWrap(buf, opts) { return hybridWrap(buf, { ecdhKey32: opts?.ecdhKey32 || (keys ? ecdhKey(keys.priv, opts?.peerPub || keys.pubkey) : null), ...opts }); }
  hybridSessionOpen(pkg, opts) { return hybridOpen(pkg, { ecdhKey32: opts?.ecdhKey32 || (keys ? ecdhKey(keys.priv, opts?.peerPub || keys.pubkey) : null), ...opts }); }
  zkProve(secret, opts) { return zkAuthProve(secret ?? syncId, opts); }
  zkVerify(t, opts) { return zkAuthVerify(t, opts); }
  edgeMapReduce(job, shards, opts) { return edgeMapReducePlan(job, shards, opts); }
  stegoEmbed(payload, opts) { return stegoEmbedPng(payload, opts); }
  stegoExtract(png, opts) { return stegoExtractPng(png, opts); }
  p2pSyncOnce(peer, digest) {
    const r = p2pSyncOncePlan(peer, digest, this._p2pState || {});
    if (r.state) this._p2pState = r.state;
    return r;
  }
  async agentTick(telemetry) {
    const tick = nodeAgentTickPlan(telemetry, { enabled: NODE_AGENT });
    for (const action of tick.actions || []) {
      try {
        if (action.op === 'heal-hint') tick.healHint = action.detail;
        else if (action.op === 'edgeMapReduce') tick.edgeHint = action.detail;
        else if (action.op === 'api-ping' && NODE_AGENT_API && typeof globalThis.fetch === 'function') {
          const res = await globalThis.fetch(NODE_AGENT_API, { method: 'HEAD' }).catch(() => globalThis.fetch(NODE_AGENT_API, { method: 'GET' }));
          tick.apiPing = res?.ok;
        }
      } catch (err) { tick.actionErrors = [...(tick.actionErrors || []), { op: action.op, error: err.message }]; }
    }
    if (telemetry?.agentMessage) {
      const msg = nodeAgentMessagePlan(telemetry.from || DEVICE, telemetry.to || 'peer', telemetry.agentMessage, {
        fromEnabled: NODE_AGENT, toEnabled: true, channelKey: syncId,
      });
      tick.agentMessage = msg;
    }
    if (NODE_AGENT && NODE_AGENT_API && typeof globalThis.fetch === 'function') {
      try {
        const res = await globalThis.fetch(NODE_AGENT_API, {
          method: 'POST',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({ telemetry, tick, ts: Date.now() }),
        });
        tick.apiPing = res.ok;
      } catch (err) {
        tick.apiError = err.message;
      }
    }
    return tick;
  }
  oracleGet(url, exits) { return oracleFetchPlan(url, exits); }
  vsearch(q, docs) { return vsearchPlan(docs || this._vdocs || [], q); }
  vdocPut(id, text) {
    const docs = this._vdocs || [];
    const doc = { id: String(id), text: String(text || ''), vec: embedLattice(text || '') };
    const i = docs.findIndex((d) => d.id === doc.id);
    if (i >= 0) docs[i] = doc; else docs.push(doc);
    this._vdocs = docs;
    this.state.vectorDocs = docs;
    saveState(this.state);
    return { ok: true, id: doc.id, n: docs.length };
  }
  vdocList() { return (this._vdocs || []).slice(); }
  crdtApply(op) {
    const r = crdtApplyPlan(this.state.crdt || {}, op);
    this.state.crdt = r.state;
    saveState(this.state);
    return r;
  }
  transportEnqueue(klass, bundle) {
    const r = transportEnqueuePlan(klass, bundle, this.state.dtnQueue || []);
    this.state.dtnQueue = r.queue;
    saveState(this.state);
    return r;
  }
  transportForward(peerMet = true) {
    const r = transportForwardPlan(this.state.dtnQueue || [], peerMet);
    this.state.dtnQueue = r.remaining;
    saveState(this.state);
    return r;
  }
  sessionVaultPut(cookies) { return sessionVaultSeal(cookies, syncId); }
  sessionVaultGet(blob) { return sessionVaultOpen(blob, syncId); }
  sessionVaultSave(cookies) {
    const sealed = sessionVaultSeal(cookies, syncId);
    if (!sealed.ok) return sealed;
    this.state.sessionVault = sealed.blob;
    saveState(this.state);
    return sealed;
  }
  sessionVaultLoad() {
    const blob = this.state.sessionVault;
    if (!blob) return { ok: false, reason: 'no-vault' };
    return sessionVaultOpen(blob, syncId);
  }
  axTree(html) { return domToAxTree(html); }
  axAct(tree, action) { return axAction(tree, action); }
  sandboxIngest(buf) { return sandboxIngest(buf); }
  cryptoShred(buf) { return cryptoShred(buf); }
  metamorph(seed) {
    const plan = metamorphPlan(seed || RUN_SALT);
    this._metamorphPlan = plan;
    return plan;
  }
  metamorphRuntime(input, handlers) {
    const plan = this._metamorphPlan || metamorphPlan(RUN_SALT);
    return metamorphDispatch(plan, input, handlers || {
      encrypt: (x) => x,
      hash: (x) => x,
      route: (x) => x,
      pad: (x) => x,
      hop: (x) => x,
    });
  }
  latticeDoors() {
    return latticeWaveDoorsCatalog().map((id) => ({
      id,
      wired: LATTICE_WIRED_DOORS.has(id),
      endpoint: LATTICE_WIRED_DOORS.has(id) ? `/api/lattice-wave/door/${id}` : undefined,
    }));
  }

  async latticeDoor(doorId, input = {}) {
    const id = String(doorId || '').toUpperCase();
    switch (id) {
      case 'S168': return bftOracleBridgePlan(input.oracle || {}, input.bftVotes || this._bftPendingVotes || [], input);
      case 'S169': return fheEdgeJobPlan(input.values || [], input.key || 'fhe-demo');
      case 'S170': return stegoDtnCarrierPlan(input.payload || Buffer.from('dtn'), this.state.dtnQueue || []);
      case 'S171': return agentAxLoopPlan(input.html || '<button>ok</button>', !!(typeof NODE_AGENT !== 'undefined' && NODE_AGENT));
      case 'S172': return crdtP2pAntiEntropyPlan(this.state.crdt || {}, input.stateB || {}, input.peer || 'peer');
      default: return { ok: false, reason: 'unknown-door', door: 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.siblingTimer);
    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 autoPullEngineOnOpen(args) {
  if (typeof ENGINE_SOURCE !== 'undefined' && ENGINE_SOURCE) return null;
  if (!engineSiblings) return null;
  const skip = new Set(['--pull-engine', '--seal-engine', '--publish-engine', '--new-id', '--siblings', '--sibling-status']);
  if (args[0] && skip.has(args[0])) return null;
  try {
    ensureId();
    deriveKeys();
    await cloud.ready();
    const before = typeof ENGINE_BODY_SEQ !== 'undefined' ? ENGINE_BODY_SEQ : 0;
    await cloud._siblingMeshTick();
    await cloud.stop();
    // אם apply החליף קבצים — מודיעים (התהליך הנוכחי עדיין על הגוף הישן בזיכרון)
    try {
      const kn = engineSiblings.extractKnobs(fs.readFileSync(__filename, 'utf8'));
      if (Number(kn.ENGINE_BODY_SEQ || 0) > Number(before)) {
        log('אחים: גוף מנוע עודכן בדיסק (seq', kn.ENGINE_BODY_SEQ, '· נערך', kn.ENGINE_UPDATED_AT || '?', ') — הרצה הבאה תשתמש בו במלואו.');
      } else if (kn.ENGINE_UPDATED_AT) {
        log('גוף מנוע מקומי · נערך לאחרונה:', kn.ENGINE_UPDATED_AT, '· seq', kn.ENGINE_BODY_SEQ);
      }
    } catch { /* ignore */ }
    return true;
  } catch (e) {
    return { ok: false, error: e.message };
  }
}

async function main() {
  const args = process.argv.slice(2);

  // עותק מבוזר (גם אחרי שליחה בוואטסאפ): משיכה מהרשת לפני המשך
  if (!(typeof ENGINE_SOURCE !== 'undefined' && ENGINE_SOURCE)) {
    await autoPullEngineOnOpen(args);
  }

  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') {
    const di = args.indexOf('--dir');
    const doPublish = args.includes('--publish');
    ensureId(); deriveKeys();
    if (!args[1]) { log('שימוש: node cloud.js --import <file.zip> [--dir <תיקייה>] [--publish]'); return; }
    if (doPublish) {
      const r = await cloud.importZipAndPublish(args[1], di > 0 ? { dir: args[di + 1] } : {});
      log('ZIP+פרסום:', r.address, r.siteDir ? `site=${r.siteDir}/` : '', r.bridge ? `bridge=${r.bridge}` : '');
      await cloud.stop();
      process.exit(r.ok ? 0 : 1);
    }
    await cloud.importZip(args[1], di > 0 ? { dir: args[di + 1] } : {});
    log('הייבוא הושלם. הרץ "node cloud.js --import-publish <zip>" או "node cloud.js --deploy" לבנייה ופרסום.');
    return;
  }

  if (args[0] === '--import-publish') {
    if (!args[1]) { log('שימוש: node cloud.js --import-publish <file.zip> [--dir <תיקייה>]'); return; }
    const di = args.indexOf('--dir');
    ensureId(); deriveKeys();
    const r = await cloud.importZipAndPublish(args[1], di > 0 ? { dir: args[di + 1] } : {});
    log('ZIP+פרסום:', r.address, r.siteDir ? `site=${r.siteDir}/` : '', r.bridge ? `bridge=${r.bridge}` : '');
    await cloud.stop();
    process.exit(r.ok ? 0 : 1);
  }

  if (args[0] === '--republish') {
    ensureId(); deriveKeys();
    const r = await cloud.projectPublishPipeline({ skipExtract: true });
    log('פרסום מחדש:', r.address);
    await cloud.stop();
    process.exit(r.ok ? 0 : 1);
  }

  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> [...] [--write]');
      log('  בלי --write: רק מדפיס את ה-SYNC_ID. עם --write: כותב אותו לתוך cloud.js (מחליף ID קיים).');
      return;
    }
    const write = args.includes('--write');
    const shareArgs = args.slice(1).filter((a) => a !== '--write');
    if (shareArgs.length < 2) { log('שימוש: node cloud.js --recover <רסיס1> <רסיס2> [...] [--write]'); return; }
    const secret = shamirJoin(shareArgs);
    log('Δ2: הסוד המשוחזר: ' + secret);
    if (!/^SC-[0-9A-F]{40}$/.test(secret)) {
      log('אזהרה: התוצאה לא נראית כמו SYNC_ID תקין — כנראה סופקו פחות רסיסים');
      log('מהסף (K) שנקבע בפיצול, או רסיס שגוי. נסה שוב עם רסיסים נוספים.');
      return;
    }
    if (write) {
      try {
        const self = fs.readFileSync(__filename, 'utf8');
        const updated = self.replace(/const SYNC_ID = '[^']*';/, `const SYNC_ID = '${secret}';`);
        if (updated === self) throw new Error('SYNC_ID marker not found');
        fs.writeFileSync(__filename, updated, 'utf8');
        try { fs.unlinkSync(STATE_FILE); } catch {}
        log('ה-SYNC_ID נכתב לתוך cloud.js. הרץ: node cloud.js --once');
        log('אזהרה: זה מחבר את התיקייה הזו לענן של ה-ID המשוחזר (ולא לענן חדש).');
      } catch (err) {
        log('שגיאה בכתיבת ה-ID:', err.message);
        log('הדבק ידנית בראש הקובץ: const SYNC_ID = \'' + secret + '\';');
      }
    } else {
      log('כדי לחבר תיקייה לענן הזה: הדבק את השורה הבאה בראש cloud.js, או הרץ שוב עם --write');
      log(`  const SYNC_ID = '${secret}';`);
    }
    return;
  }

  if (args[0] === '--recovery-kit') {
    ensureId(); deriveKeys();
    const kit = await cloud.exportRecoveryKit({ writeFile: true });
    log('Recovery kit:', kit.path || '(memory)');
    log('fingerprint:', kit.kit?.fingerprint);
    log('כנה: הערכה לא כוללת SYNC_ID — בלי ID/רסיסים אי אפשר לפענח.');
    return;
  }

  if (args[0] === '--seal-engine' || args[0] === '--publish-engine') {
    if (!engineSiblings) { log('engine-siblings.js חסר ליד cloud.js'); return; }
    try {
      engineSiblings.assertCanonicalSource(__filename);
    } catch (e) {
      log('נדחה:', e.message);
      return;
    }
    const verIdx = args.indexOf('--version');
    const version = verIdx >= 0 ? args[verIdx + 1] : ENGINE_VERSION;
    const pkg = engineSiblings.buildPackage(__filename, { version });
    const outDir = path.join(ROOT, '.cloud-engine-dist');
    const outPkg = path.join(outDir, 'engine-package.json');
    engineSiblings.writePackageFile(pkg, outPkg);
    // תבנית Desktop = loader + seal (לא קוד קריא)
    const demoDir = path.join(outDir, 'distributed-sample');
    fs.mkdirSync(demoDir, { recursive: true });
    engineSiblings.applyPackageToFile(path.join(demoDir, 'cloud.js'), pkg, { preserveKnobs: false });
    log('Sealed+encrypted engine seq=', pkg.seq, 'version=', pkg.version);
    log('package:', outPkg);
    log('sample loader:', path.join(demoDir, 'cloud.js'));
    log('sample seal:', path.join(demoDir, 'cloud.engine.seal'));
    log('canonical root:', ENGINE_SOURCE_ROOT);
    log('pub:', String(pkg.pub).slice(0, 24) + '…');
    if (args[0] === '--publish-engine') {
      ensureId(); deriveKeys();
      // 1) עדכון מקומי של פרויקטים/אחים מיושנים לפני הרשת (מיידי)
      try {
        const localDirs = engineSiblings.collectLocalSiblingDirs(ROOT, {
          extraRoots: [
            path.join(require('node:os').homedir(), 'AppData', 'Roaming', 'SkyCloud Desktop'),
            path.join(ROOT, 'ui'),
          ],
        });
        const localRes = engineSiblings.propagatePackage(pkg, localDirs, { force: false });
        const updated = localRes.filter((r) => r.ok);
        const lagging = localRes.filter((r) => r.skipped);
        const failed = localRes.filter((r) => r.ok === false && !r.skipped);
        log(`אחים מקומיים: עודכנו ${updated.length} · כבר מעודכנים/דולגו ${lagging.length} · כשל ${failed.length}`);
        for (const r of updated.slice(0, 20)) log('  ✓', r.dir, '→ seq', r.seq || pkg.seq);
        for (const r of failed.slice(0, 8)) log('  ✗', r.dir, r.error);
        const report = engineSiblings.outdatedSiblingReport(ROOT, pkg.seq);
        if (report.outdated?.length) {
          log('אחים במפקד שמפגרים ב־seq (ימשכו בפתיחה / אחרי publish רשת):');
          for (const p of report.outdated.slice(0, 15)) {
            log('  •', p.path || p.pathHash, 'seq=', p.engineSeq, '<', pkg.seq);
          }
        } else {
          log('מפקד מקומי: אין peers מפגרים ידועים (או שטרם נרשמו).');
        }
      } catch (e) {
        log('אזהרה: סריקת אחים מקומית:', e.message);
      }
      // 2) פרסום רשת במצב lite — בלי ready()/startLive (שתוקעים על אתר/Living)
      log('אחים: מעלה חבילה ל־Blossom + Roam (lite)…');
      cloud._liteMode = true;
      await cloud.readyLite({ minRelays: 2, timeoutMs: 12_000 });
      const buf = Buffer.from(JSON.stringify(pkg), 'utf8');
      log('אחים: Blossom upload…', Math.round(buf.length / 1024), 'KB');
      const up = await blossomUpload(buf, 'application/json', { timeoutMs: 14_000 });
      const name = ENGINE_CHANNEL || 'skycloud-engine-siblings-v1';
      log('אחים: publishName', name, '→', up.hash.slice(0, 16) + '…');
      const roam = await cloud.publishName(name, 'blossom:' + up.hash, { lite: true, waitMs: 1000 });
      const meta = {
        channel: name, seq: pkg.seq, version: pkg.version,
        blossomHash: up.hash, roamSeq: roam.seq, publishedAt: new Date().toISOString(),
      };
      fs.writeFileSync(path.join(outDir, 'published.json'), JSON.stringify(meta, null, 2));
      log('Published to sibling channel:', name, '→ blossom:' + up.hash, 'roamSeq=', roam.seq);
      try {
        engineSiblings.recordLocalSibling(ROOT, {
          knobs: { ENGINE_CHANNEL: name, ENGINE_BODY_SEQ: pkg.seq },
          canonical: true,
          event: 'seen',
        });
        const rpack = engineSiblings.exportRosterPackage(ROOT);
        log('אחים: מפרסם roster מוצפן…');
        const rup = await blossomUpload(Buffer.from(JSON.stringify(rpack), 'utf8'), 'application/json', { timeoutMs: 12_000 });
        await cloud.publishName(name + '-roster', 'blossom:' + rup.hash, { lite: true, waitMs: 800 });
        log('Roster (encrypted) published →', name + '-roster');
      } catch (e) {
        log('אזהרה: פרסום roster:', e.message);
      }
      await cloud.stop();
      process.exit(0);
    }
    return;
  }

  if (args[0] === '--siblings' || args[0] === '--sibling-status') {
    if (!engineSiblings) { log('engine-siblings.js חסר'); return; }
    const aes = engineSiblings.resolveAesForRoster(ROOT);
    if (!aes) { log('אין מפתח אחים (seal/signing) — הרץ pull/seal קודם'); return; }
    // רענון מהרשת אם אפשר — lite בלבד (ready() מלא תוקע על אתר/Living)
    try {
      ensureId(); deriveKeys();
      cloud._liteMode = true;
      await cloud.readyLite({ minRelays: 2, timeoutMs: 12_000 });
      await cloud._siblingMeshTick();
      await cloud.stop();
    } catch (e) {
      log('רשת (חלקי):', e.message);
    }
    const roster = engineSiblings.loadRoster(ROOT, aes);
    const sum = engineSiblings.rosterSummary(roster, aes);
    log('מפקד אחים (מוצפן במקומי; מפתח רק למנוע):');
    log('  totalEver=', sum.totalEver, 'alive≈', sum.alive, 'gone=', sum.gone, 'moved=', sum.moved);
    log('  latestEngineSeq=', sum.latestEngineSeq);
    for (const a of sum.sampleAlive.slice(0, 12)) {
      log('  •', a.canonical ? '[SOURCE]' : '[peer]', a.path || a.pathHash, 'seq=', a.engineSeq, a.platform || '');
    }
    log('כנות:', sum.honesty);
    return;
  }

  if (args[0] === '--pull-engine') {
    if (!engineSiblings) { log('engine-siblings.js חסר — לא ניתן למשוך גוף'); return; }
    const localPkg = path.join(ROOT, '.cloud-engine-dist', 'engine-package.json');
    const force = args.includes('--force');
    let pkg = null;
    // 1) קובץ מקומי (הפצה מ-Desktop/מקור)
    if (fs.existsSync(localPkg)) {
      try { pkg = engineSiblings.readPackageFile(localPkg); } catch (e) { log('חבילה מקומית פגומה:', e.message); }
    }
    // 2) רשת — Roam + Blossom
    if (!pkg || args.includes('--network')) {
      try {
        ensureId(); deriveKeys();
        await cloud.ready();
        const name = ENGINE_CHANNEL || 'skycloud-engine-siblings-v1';
        const resolved = await cloud.resolveName(name);
        if (resolved?.target && String(resolved.target).startsWith('blossom:')) {
          const hash = String(resolved.target).slice('blossom:'.length);
          let raw = null;
          for (const base of BLOSSOM_SERVERS) {
            try {
              const res = await fetch(base.replace(/\/$/, '') + '/' + hash);
              if (res.ok) { raw = Buffer.from(await res.arrayBuffer()); break; }
            } catch { /* try next */ }
          }
          if (raw) {
            pkg = JSON.parse(raw.toString('utf8'));
            log('נמשכה חבילת מנוע מהרשת seq=', pkg.seq);
          }
        }
        await cloud.stop();
      } catch (e) {
        log('משיכת רשת נכשלה (ייתכן שאין פרסום עדיין):', e.message);
      }
    }
    if (!pkg) { log('אין חבילת מנוע זמינה (--seal-engine במקור, או --network אחרי publish)'); return; }
    if (!force && !engineSiblings.shouldApply(pkg, __filename)) {
      log('כבר מעודכן (seq מקומי ≥', pkg.seq, ')');
      return;
    }
    if (ENGINE_SOURCE && !force) {
      log('במקור (ENGINE_SOURCE) לא מחליפים גוף מ־pull — רק מפרסמים החוצה. השתמש --force אם בטוח.');
      return;
    }
    const r = engineSiblings.applyPackageToFile(__filename, pkg);
    log('עודכן גוף מנוע:', r.version, 'seq=', r.seq, 'SYNC_ID נשמר=', r.syncId);
    try {
      engineSiblings.recordLocalSibling(ROOT, {
        knobs: { ENGINE_CHANNEL: ENGINE_CHANNEL, ENGINE_BODY_SEQ: pkg.seq },
        canonical: false,
        event: 'seen',
      });
    } catch { /* ignore */ }
    return;
  }

  if (args[0] === '--address-stack') {
    ensureId(); deriveKeys();
    const st = loadState();
    const slug = st.skyHandle || SITE_SLUG;
    const h = skyHandleOf(slug);
    const virt = virtualAddressForPack(st);
    const plan = st.addressStack || addressStackPlan({
      canonical: globalAddress(),
      sky: h.sky,
      at: h.atUrl(),
      domain: CUSTOM_DOMAIN || null,
      callingCard: st.callingCard || null,
      mirrors: mirrorAddresses(),
      virtual: virt && virt.ok !== false ? virt : null,
      npub: npub(),
      slug: slugify(slug),
    });
    if (virt?.short && !plan.short) {
      plan.short = virt.short;
      plan.virtual = true;
      plan.host = virt.host;
      plan.suffix = virt.suffix;
      plan.open = virt.open || plan.open;
      plan.npub = virt.npub || plan.npub;
    }
    process.stdout.write(JSON.stringify(plan) + '\n');
    return;
  }

  if (args[0] === '--access-pack' || args[0] === '--mirrors') {
    ensureId(); deriveKeys();
    const ap = gatewayAccessPackPlan({ domain: CUSTOM_DOMAIN || null, virtual: virtualAddressForPack() });
    log(ap.pack.shareTextHe);
    log('—');
    log('JSON: GET /api/access · דף: …/access.html · preferredGateway=', preferredGatewayHost());
    return;
  }

  if (args[0] === '--connect-plan') {
    const scope = (args[1] && !String(args[1]).startsWith('--')) ? args[1] : 'auto';
    const zipIdx = args.indexOf('--zip');
    const fileIdx = args.indexOf('--file');
    const folderIdx = args.indexOf('--folder');
    const plan = inferConnectScopePlan(scope, {
      zipPath: zipIdx >= 0 ? args[zipIdx + 1] : undefined,
      relFile: fileIdx >= 0 ? args[fileIdx + 1] : undefined,
      relFolder: folderIdx >= 0 ? args[folderIdx + 1] : undefined,
      preferData: args.includes('--data'),
    });
    log('חיבור לענן — מצב:', plan.mode, '—', plan.intentHe || '');
    for (const s of plan.steps || []) {
      if (s.cmd) log('  →', s.cmd, s.why ? ('— ' + s.why) : '');
      else if (s.op === 'place') log('  → שים', s.what, 'ב־', s.where);
      else if (s.key) log('  →', s.key + (s.value != null ? '=' + s.value : ''), s.why ? ('— ' + s.why) : '');
      else if (s.what) log('  →', s.op, s.what, s.n != null ? '(n=' + s.n + ')' : '');
    }
    if (plan.addressUpdates) log('כתובת:', plan.addressUpdates);
    if (plan.rebuild) log('בנייה מחדש:', plan.rebuild);
    log('פירוט: AI-AGENT-GUIDE §2.9 · USER-GUIDE');
    return;
  }

  if (args[0] === '--embed-client') {
    ensureId(); deriveKeys();
    const embedSync = args.includes('--embed-sync-id');
    const out = cloud.embedClientScaffold({ embedProjectSync: embedSync });
    log('Native embed:', out.manifestPath);
    log('Bridge:', out.bridgePath);
    log('כנה: המנוע לא קומפל APK/EXE — scaffold + ערוץ עדכון. בנייה בחנות = כלי הפלטפורמה.');
    if (embedSync) log('אזהרה: SYNC_ID הוטמע במניפסט — רק ללקוח סנכרון פרטי, לא לחנות ציבורית.');
    return;
  }

  if (args[0] === '--import-local') {
    ensureId(); deriveKeys();
    await cloud.ready();
    const rep = await cloud.importLocalData();
    log('ייבוא מקומי:', rep.n, 'פריטים');
    for (const i of (rep.imported || []).slice(0, 12)) {
      log(' ', i.error ? 'FAIL' : 'OK', i.rel, '→', i.key, i.error || '');
    }
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--client-update') {
    ensureId(); deriveKeys();
    await cloud.ready();
    const verIdx = args.indexOf('--version');
    const version = verIdx >= 0 ? args[verIdx + 1] : undefined;
    const noUpload = args.includes('--no-upload');
    const out = await cloud.publishClientUpdate({ version, upload: !noUpload });
    log('Client update:', out.manifest?.version, 'digest=', out.manifest?.digest);
    log('artifacts:', (out.artifacts || []).length, out.path || '');
    log('ערוץ מתקינים נפרד מפרסום האתר — בדוק GET /api/client/update');
    await cloud.stop();
    process.exit(0);
  }

  if (args[0] === '--pack') {
    const dir = (args[1] && !String(args[1]).startsWith('--')) ? args[1] : (SITE_DIR || 'site');
    const oi = args.indexOf('--out');
    const out = oi >= 0 ? args[oi + 1] : undefined;
    const keepId = args.includes('--keep-id');
    const packed = await cloud.packCapsule(dir, { out, keepId });
    log('Capsule pack מוכן:', packed.out);
    return;
  }

  if (args[0] === '--handle') {
    ensureId(); deriveKeys();
    await cloud.ready();
    const slug = (args[1] && !String(args[1]).startsWith('--')) ? args[1] : SITE_SLUG;
    const h = await cloud.publishHandle(slug);
    if (args.includes('--card')) await cloud.publishCallingCard({ handle: h.handle });
    log('קנונית:', h.canon);
    log('מיוחדת:', h.sky, '·', h.atUrl());
    await cloud.stop();
    process.exit(0);
  }

  if (args.includes('--bft-status')) {
    ensureId(); deriveKeys();
    await cloud.ready();
    const st = cloud.bftStatus();
    log('S151 BFT:', JSON.stringify(st));
    await cloud.stop();
    return;
  }
  if (args.includes('--drain-continuum')) {
    ensureId(); deriveKeys(); await cloud.ready();
    const r = await cloud.drainMindContinuum({ limit: Number(args[args.indexOf('--drain-continuum') + 1]) || 5 });
    log('Continuum drain:', JSON.stringify({ ok: r.ok, n: r.n, remaining: r.remaining }));
    await cloud.stop(); return;
  }
  if (args.includes('--trust-heal')) {
    ensureId(); deriveKeys(); await cloud.ready();
    const r = await cloud.runAutonomicTrustHeal({});
    log('Trust heal:', JSON.stringify(r));
    await cloud.stop(); return;
  }
  if (args.includes('--gossip-seeds')) {
    ensureId(); deriveKeys(); await cloud.ready();
    const r = await cloud.gossipVisitorSeeds({ warmCdn: true });
    log('Gossip seeds:', JSON.stringify({ ok: r.ok, primary: r.primary?.url || null }));
    await cloud.stop(); return;
  }
  if (args.includes('--clear-fed-receipts')) {
    ensureId(); deriveKeys(); await cloud.ready();
    const r = await cloud.clearCrossProjectReceipts({});
    log('Clear fed receipts:', JSON.stringify(r));
    await cloud.stop(); return;
  }
  if (args[0] === '--edge-job') {
    ensureId();
    let job; try { job = JSON.parse(args[1] || '{}'); } catch { log('שימוש: --edge-job \'{"op":"matmul",...}\''); return; }
    const shards = job.shards || [1, 2];
    const r = edgeMapReducePlan(job.program || job, shards, { quorum: job.quorum || 2, authors: ['A', 'B', 'C'] });
    log('S155 Edge:', JSON.stringify({ ok: r.ok, digest: r.quorum?.digest }));
    return;
  }
  if (args[0] === '--oracle-get') {
    const url = args[1];
    if (!url) { log('שימוש: --oracle-get <url>'); return; }
    log('S161 Oracle: סימולציית קונצנזוס מקומית (אין יציאות רשת בבדיקה זו)');
    const body = 'oracle-demo';
    const h = hex(sha256(Buffer.from(body)));
    const r = oracleFetchPlan(url, [
      { author: 'e1', bodyHash: h }, { author: 'e2', bodyHash: h }, { author: 'e3', bodyHash: h },
    ]);
    log(JSON.stringify(r));
    return;
  }
  if (args.includes('--status')) {
    ensureId();
    deriveKeys();
    const st = loadState();
    const h = skyHandleOf(st.skyHandle || SITE_SLUG);
    const virt = virtualAddressForPack(st);
    log('SYNC_ID:', syncId);
    if (typeof ENGINE_BODY_SEQ !== 'undefined') log('מנוע seq:', ENGINE_BODY_SEQ, '· נערך לאחרונה:', ENGINE_UPDATED_AT || '(מקור/לא חתום)');
    log('שם הפרויקט:', PROJECT_NAME, `[${SITE_SLUG}]`);
    if (virt?.short) log('כתובת קצרה (סיומת המנוע):', virt.short, `[${virt.host}]`);
    log('כתובת קנונית (npub — ארוכה בהכרח):', globalAddress());
    if (virt?.npub) log('npub:', virt.npub);
    log('כניסה פעילה (/@):', virt?.open || h.atUrl());
    log('כתובת מיוחדת (SkyHandle):', h.sky, '· כניסה:', h.atUrl());
    if (st.callingCard) log('calling-card (Blossom):', st.callingCard);
    if (PUBLIC_WEBSITE) for (const m of mirrorAddresses().slice(1)) log('  מראה:', m);
    if (CUSTOM_DOMAIN) printDomainSetup();
    if (st.livingAddress || LIVING_ADDRESS) {
      log('Living Address:', st.livingPlan?.mode || 'living-mesh', '· staticOnly=false · mesh+∇+snapshots');
    }
    const plan = inferProjectPublishPlan(ROOT);
    const sd = resolveSiteDir();
    if (sd) log('תיקיית האתר:', sd + '/');
    else if (plan.rootIndex) log('תיקיית האתר: (שורש הפרויקט / index.html)');
    const bat = plan.batEntry || resolveBatEntry();
    if (bat) log(`קובץ הפעלה: ${bat.bat} → דף הבית: ${bat.html}`);
    if (plan.build.command) log('פקודת בנייה:', plan.build.command, plan.build.source !== 'config' ? `(${plan.build.source})` : '');
    else if (BUILD_COMMAND) log('פקודת בנייה:', BUILD_COMMAND);
    log('מוכנות פרסום מלא:', plan.ready ? 'כן' : 'חלקי — בדוק SITE_DIR/BUILD/PUBLIC_WEBSITE');
    try {
      await cloud.ready();
      const pr = cloud.publishReadiness();
      log('publishReadiness:', JSON.stringify(pr));
      await cloud.stop();
    } catch { /* status without live node */ }
    log('משתמשי קצה (דפדפן):', '/cloud-web.js · auth · userSet · save');
    const native = inferNativeClientPlan(ROOT);
    log('Living לא-סטטי:', native.livingNotStatic.ok ? 'כן (full-app+living+public)' : 'חסר knobs');
    log('APK/EXE ארטיפקטים:', native.artifacts.length, native.shells.map((s) => s.kind).join(',') || '(אין מעטפת)');
    log('כניסה באפליקציה:', native.login.hasLogin ? 'זוהתה → login-sync' : 'לא זוהתה (מבקר/ציבורי)');
    log('ייבוא מקומי מוכן:', native.localImport.n, 'קבצים תחת data|local|…');
    log('ערוצים:', 'כתובת=--deploy · מתקינים=--client-update · הטמעה=--embed-client');
    if (PUBLIC_WEBSITE) {
      const ap = gatewayAccessPackPlan({ domain: CUSTOM_DOMAIN || null, virtual: virt });
      log('גישה נגד חסימות: שער מועדף=', preferredGatewayHost(), '· מראות=', ap.nMirrors);
      log('  שתף רשימה: node cloud.js --access-pack  · דף /access.html');
    }
    log('נתונים קיימים:', plan.dataContinuity.never);
    const stepList = [...plan.steps.slice(0, 4), ...native.steps.filter((s) => s.op === 'run').slice(0, 4)];
    for (const s of stepList) {
      if (s.op === 'run' || s.op === 'verify') log('  →', s.cmd, '—', s.why);
      else if (s.op === 'set' || s.op === 'ensure') log('  →', s.key + '=' + JSON.stringify(s.value), '—', s.why);
      else if (s.op === 'optional') log('  → (אופציונלי)', s.key, s.value, '—', s.why);
    }
    if (CAPSULE_EMBED?.b64) log('S16 Capsule embed:', CAPSULE_EMBED.nFiles || '?', 'קבצים');
    log('קבצים במעקב מקומי:', Object.keys(st.files ?? {}).length);
    log('Offline queue depth:', (st.offlineQueue || []).length);
    log('Node agent:', NODE_AGENT ? 'on' : 'off');
    log('Lattice doors:', latticeWaveDoorsCatalog().join(','));
    log('פירוט סוכן: AI-AGENT-GUIDE §2.7–§2.8 · USER-GUIDE');
    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);
  }
  // SkyMind: LLM חיצוני דרך המנוע (∇→Δ→cache→API)
  if (args.includes('--mind-api') || MIND.enabled) {
    cloud.configureMind({ enabled: true });
    cloud.enableDbApi();
    cloud.enableMindApi({ enabled: true });
    cloud.bindMindToPulse();
    log('SkyMind פעיל — POST /api/mind/ask · מפתח מ-ENV (SKYCLOUD_LLM_KEY / OPENAI_API_KEY)');
  }
  // Apparent Physics Lattice — mesh + CDN + address + SLA
  if (args.includes('--physics-lattice')) {
    await cloud.enablePhysicsLattice({ address: { card: true } }).catch((err) => log('Physics Lattice:', err.message));
    const sla = cloud.slaReport();
    log(`SLA score=${sla.sla.score} workers=${sla.sla.workersAlive} · apparentOps/s≈${sla.throughput.apparentOpsPerSec}`);
  }
  // Living Global Address — כתובת = Node מלא (nsite/CUSTOM_DOMAIN אותו נתיב)
  if (args.includes('--living-address') || (LIVING_ADDRESS && !cloud._livingAddressReady && !args.includes('--side-effect-node'))) {
    const living = await cloud.enableLivingAddress({
      force: args.includes('--living-address'),
      republish: true,
      seed: true,
      card: !!CUSTOM_DOMAIN || args.includes('--card'),
      mind: args.includes('--mind-api') || MIND.enabled,
    }).catch((err) => ({ error: err.message }));
    if (living.error) log('Living Address נכשל:', living.error);
    else {
      log('Living Address:', living.mode, '· staticOnly=' + living.staticOnly, '· gate=' + (living.gate && living.gate.ok));
      if (args[0] === '--living-address' && !args.includes('--deploy') && !args.includes('--once')) {
        await cloud.stop();
        process.exit(0);
      }
    }
  }
  if (args.includes('--side-effect-node')) {
    const se = await cloud.enableSideEffectNode({
      force: true,
      fullNode: args.includes('--full-node') || FULL_NODE,
      bridge: LOCAL_BRIDGE || undefined,
      mind: args.includes('--mind-api') || MIND.enabled,
    }).catch((err) => ({ error: err.message }));
    if (se.error) log('Side-Effect Node נכשל:', se.error);
    else log('Side-Effect Node:', se.dial?.channel || 'dial', '· bridge=' + (se.bridge || '—'), '· fullNode=' + !!se.fullNode);
    if (args[0] === '--side-effect-node' && !args.includes('--deploy') && !args.includes('--once')) {
      await cloud.stop();
      process.exit(0);
    }
  }
  if (args.includes('--space-mesh') || (SPACE_MESH.enabled && args.includes('--living-address'))) {
    const sm = await cloud.enableSpaceMesh({ force: true }).catch((err) => ({ error: err.message }));
    if (sm.error) log('SpaceMesh נכשל:', sm.error);
    else {
      log('SpaceMesh: phase=' + sm.dance?.phase, '· diversity=' + sm.dance?.diversity?.score, '· CGR=' + (sm.route?.ok ? sm.route.hops + ' hops' : 'dark'));
      log('  כנה: DTN/CGR control plane — לא שיגור לוויין; ריקוד נתיבים + custody always-on');
    }
    if (args[0] === '--space-mesh' && !args.includes('--deploy') && !args.includes('--once')) {
      await cloud.stop();
      process.exit(0);
    }
  }
  if (args.includes('--immortal') || args.includes('--replicate-self')) {
    const im = await cloud.enableImmortalMesh({ force: true, skipPack: args.includes('--skip-pack') }).catch((err) => ({ error: err.message }));
    if (im.error) log('Immortal נכשל:', im.error);
    else {
      log('Immortal: tip=' + (im.tip?.digest || '').slice(0, 16), '· antennas=' + im.antennas?.n, '· satPaths=' + (im.pathClasses || []).filter((p) => p.satelliteLikely).length);
      log('  חי על אנטנות הרשת (relays/blossom/nsite) — המקור יכול להיות כבוי; שכפולים מתעדכנים לפי tip');
    }
    if ((args[0] === '--immortal' || args[0] === '--replicate-self') && !args.includes('--deploy') && !args.includes('--once') && !args.includes('--ado')) {
      await cloud.stop();
      process.exit(0);
    }
  }
  if (args.includes('--ado')) {
    const ado = await cloud.enableADO({ force: true, skipPack: args.includes('--skip-pack') }).catch((err) => ({ error: err.message }));
    if (ado.error) log('ADO נכשל:', ado.error);
    else {
      log('ADO: member=' + (ado.member?.digest || '').slice(0, 16), '· brains=' + ado.quorum?.nBrains, '· citizen=' + !!ado.passport?.citizen);
      log('  כל עותק = מוח+חבר · Zero-Trust swarm על אנטנות ציבוריות · לא בוטנט/נשק');
    }
    if (args[0] === '--ado' && !args.includes('--deploy') && !args.includes('--once') && !args.includes('--sios') && !args.includes('--adis')) {
      await cloud.stop();
      process.exit(0);
    }
  }
  if (args.includes('--sios')) {
    const s = await cloud.enableSIOS({ force: true }).catch((err) => ({ error: err.message }));
    if (s.error) log('SIOS נכשל:', s.error);
    else log('SIOS: plugins=' + Object.keys(s.manifest?.plugins || {}).filter((k) => s.manifest.plugins[k]).join(','));
    if (args[0] === '--sios' && !args.includes('--adis') && !args.includes('--deploy') && !args.includes('--once')) {
      await cloud.stop();
      process.exit(0);
    }
  }
  if (args.includes('--adis')) {
    const a = await cloud.enableADIS({ force: true, slmAuto: args.includes('--slm') || ADIS.slmAuto }).catch((err) => ({ error: err.message }));
    if (a.error) log('ADIS נכשל:', a.error);
    else {
      log('ADIS: kernel=' + (a.kernel?.digest || '').slice(0, 16), '· flock=' + (a.flock?.mindDigest || '').slice(0, 16));
      log('  Organic AI Swarm — Cognitive Kernel primary; SLM/API = fused lobes; mesh-prefer infer');
    }
    if (args[0] === '--adis' && !args.includes('--deploy') && !args.includes('--once')) {
      await cloud.stop();
      process.exit(0);
    }
  }
  // S37: פריסת קפסולה ל-mesh (+ עובדים אחרים ימשכו ב-heal אם _autoPullCapsule)
  if (args[0] === '--capsule-mesh' || args.includes('--capsule-mesh')) {
    const dir = (args[1] && !String(args[1]).startsWith('--')) ? args[1] : (SITE_DIR || 'site');
    cloud._autoPullCapsule = true;
    await cloud.enablePhysicsLattice({ address: { card: false } }).catch(() => {});
    const dep = args.includes('--canary')
      ? await cloud.capsuleCanaryMesh(dir, { keepId: args.includes('--keep-id'), promote: args.includes('--promote') })
      : await cloud.deployCapsuleToMesh(dir, { keepId: args.includes('--keep-id') });
    log(args.includes('--canary') ? 'Capsule canary tip:' : 'Capsule mesh tip:', dep.tip?.digest || dep.digest);
    if (!args.includes('--deploy') && !args.includes('--once') && args[0] === '--capsule-mesh') {
      await cloud.stop();
      process.exit(0);
    }
  }
  // S45: Visitor Seed Capsule (stub ∇ לדפדפן)
  if (args.includes('--visitor-seed')) {
    await cloud.enablePhysicsLattice({ address: { card: true }, seed: true }).catch(() => {});
    const seed = await cloud.publishVisitorSeed({}).catch((err) => ({ error: err.message }));
    log('Visitor Seed:', seed.url || seed.error);
    if (args[0] === '--visitor-seed') { await cloud.stop(); process.exit(0); }
  }
  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),
    importZipAndPublish: (zip, o) => cloud.importZipAndPublish(zip, o),
    projectPublishPipeline: (o) => cloud.projectPublishPipeline(o),
    probeDevServer: (o) => cloud.probeDevServer(o),
    stopDevServer: () => cloud.stopDevServer(),
    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,
    // SkyHandle · Pack · Secret doors
    publishHandle: (h) => cloud.publishHandle(h),
    resolveHandle: (h, o) => cloud.resolveHandle(h, o),
    publishVirtualAddress: (o) => cloud.publishVirtualAddress(o),
    resolveVirtualAddress: (h, o) => cloud.resolveVirtualAddress(h, o),
    publishCallingCard: (o) => cloud.publishCallingCard(o),
    creditFromReceipt: (s, o, opts) => cloud.creditFromReceipt(s, o, opts),
    receiptPay: (s, o, opts) => cloud.receiptPay(s, o, opts),
    quoteSealed: (p, c, o) => cloud.quoteSealed(p, c, o),
    openQuote: (q, c) => cloud.openQuote(q, c),
    publishQuote: (p, c, o) => cloud.publishQuote(p, c, o),
    settleQuote: (q, c, o) => cloud.settleQuote(q, c, o),
    runVisitorShards: (d, i, p, o) => cloud.runVisitorShards(d, i, p, o),
    adoptForeignPolicy: (pub, n, o) => cloud.adoptForeignPolicy(pub, n, o),
    syncIntent: (n, o) => cloud.syncIntent(n, o),
    applySchemaIntent: (d, n) => cloud.applySchemaIntent(d, n),
    publishUiReceipt: (r, o) => cloud.publishUiReceipt(r, o),
    fetchForeignDbTip: (p, w) => cloud.fetchForeignDbTip(p, w),
    applyForeignCheckpoint: (t, o) => cloud.applyForeignCheckpoint(t, o),
    publishMigration: (p) => cloud.publishMigration(p),
    applyMigration: (b, o) => cloud.applyMigration(b, o),
    ensureZeroOriginContinuity: (o) => cloud.ensureZeroOriginContinuity(o),
    mergeContinuityOnReturn: () => cloud.mergeContinuityOnReturn(),
    payThenSeal: (ns, k, v, s, c, o) => cloud.payThenSeal(ns, k, v, s, c, o),
    freezePinOffline: (o) => cloud.freezePinOffline(o),
    queryPureBound: (d, o) => cloud.queryPureBound(d, o),
    capsuleMeshMigrate: (p) => cloud.capsuleMeshMigrate(p),
    enableDoorsApi: (o) => cloud.enableDoorsApi(o),
    configureMind: (o) => cloud.configureMind(o),
    askMind: (i, o) => cloud.askMind(i, o),
    askMindRaced: (i, o) => cloud.askMindRaced(i, o),
    mindPrefetch: (p, o) => cloud.mindPrefetch(p, o),
    mindStats: () => cloud.mindStats(),
    latencyServe: (n, p, o) => cloud.latencyServe(n, p, o),
    echoDistill: (n, c, o) => cloud.echoDistill(n, c, o),
    bindMindToPulse: (o) => cloud.bindMindToPulse(o),
    runTelemetryAutopilot: (p) => cloud.runTelemetryAutopilot(p),
    enableMindApi: (o) => cloud.enableMindApi(o),
    ensureAddressStack: (o) => cloud.ensureAddressStack(o),
    beatWorker: (o) => cloud.beatWorker(o),
    syncMeshPeers: () => cloud.syncMeshPeers(),
    meshBatch: (ops, o) => cloud.meshBatch(ops, o),
    publishCdnLattice: () => cloud.publishCdnLattice(),
    probeCdnLattice: (o) => cloud.probeCdnLattice(o),
    connectScopePlan: (scope,o) => inferConnectScopePlan(scope,o), nativeClientPlan: (o) => cloud.nativeClientPlan(o), embedClientScaffold: (o) => cloud.embedClientScaffold(o), importLocalData: (o) => cloud.importLocalData(o), publishClientUpdate: (o) => cloud.publishClientUpdate(o), clientLoginSync: (o) => cloud.clientLoginSync(o), publishReadiness: (o) => cloud.publishReadiness(o), syncUxStatus: (o) => cloud.syncUxStatus(o), exportRecoveryKit: (o) => cloud.exportRecoveryKit(o), notifyOpsAlert: (k,d,o) => cloud.notifyOpsAlert(k,d,o), slaReport: () => cloud.slaReport(),
    enablePhysicsLattice: (o) => cloud.enablePhysicsLattice(o),
    enableLivingAddress: (o) => cloud.enableLivingAddress(o),
    enableSideEffectNode: (o) => cloud.enableSideEffectNode(o),
    openDedicatedMeshDial: (o) => cloud.openDedicatedMeshDial(o),
    enableSpaceMesh: (o) => cloud.enableSpaceMesh(o),
    dtnSend: (p, o) => cloud.dtnSend(p, o),
    dtnCustodySweep: (o) => cloud.dtnCustodySweep(o),
    militaryChannelStep: (p, o) => cloud.militaryChannelStep(p, o),
    spaceMeshReport: () => cloud.spaceMeshReport(),
    enableImmortalMesh: (o) => cloud.enableImmortalMesh(o),
    enableADO: (o) => cloud.enableADO(o),
    replicateSelf: (o) => cloud.replicateSelf(o),
    immortalSweep: (o) => cloud.immortalSweep(o),
    probeNetworkAntennas: (o) => cloud.probeNetworkAntennas(o),
    vaultPut: (n, d, o) => cloud.vaultPut(n, d, o),
    vaultGet: (n, o) => cloud.vaultGet(n, o),
    runSubMind: (t, o) => cloud.runSubMind(t, o),
    pickBridgeRendezvous: (b, o) => cloud.pickBridgeRendezvous(b, o),
    publishLivingSnapshots: (o) => cloud.publishLivingSnapshots(o),
    deployCapsuleToMesh: (d, o) => cloud.deployCapsuleToMesh(d, o),
    pullCapsuleFromMesh: (o) => cloud.pullCapsuleFromMesh(o),
    askMindVerified: (i, o) => cloud.askMindVerified(i, o),
    askMindOnMesh: (i, o) => cloud.askMindOnMesh(i, o),
    hotGenomeCapsuleSwap: (g, o) => cloud.hotGenomeCapsuleSwap(g, o),
    attestTrustQuorum: (o) => cloud.attestTrustQuorum(o),
    attestCapsule: (e) => cloud.attestCapsule(e),
    verifiedMindDistill: (i, n, o) => cloud.verifiedMindDistill(i, n, o),
    capsuleCanaryMesh: (d, o) => cloud.capsuleCanaryMesh(d, o),
    promoteCapsuleCanary: (o) => cloud.promoteCapsuleCanary(o),
    rollbackCapsuleCanary: (o) => cloud.rollbackCapsuleCanary(o),
    publishVisitorSeed: (o) => cloud.publishVisitorSeed(o),
    payMindWithReceipt: (i, o) => cloud.payMindWithReceipt(i, o),
    federateTrustTip: (p, o) => cloud.federateTrustTip(p, o),
    enableAutonomicCapsuleHeal: (on) => cloud.enableAutonomicCapsuleHeal(on),
    zeroOriginMind: (i, o) => cloud.zeroOriginMind(i, o),
    mergeMindOnReturn: (o) => cloud.mergeMindOnReturn(o),
    latencyPricedMind: (i, o) => cloud.latencyPricedMind(i, o),
    publishQuorumDistillCdn: (i, n, o) => cloud.publishQuorumDistillCdn(i, n, o),
    settleReceiptMarket: (o) => cloud.settleReceiptMarket(o),
    adoptForeignCapsule: (p, o) => cloud.adoptForeignCapsule(p, o),
    enqueueMindContinuum: (i, o) => cloud.enqueueMindContinuum(i, o),
    drainMindContinuum: (o) => cloud.drainMindContinuum(o),
    settleWorkerEarnings: (o) => cloud.settleWorkerEarnings(o),
    gossipVisitorSeeds: (o) => cloud.gossipVisitorSeeds(o),
    rotateTrustRoot: (r, o) => cloud.rotateTrustRoot(r, o),
    bridgeSettlementEarnings: (o) => cloud.bridgeSettlementEarnings(o),
    federatedCanaryMarket: (p, o) => cloud.federatedCanaryMarket(p, o),
    drainDistillSeedContinuum: (o) => cloud.drainDistillSeedContinuum(o),
    checkTrustRootCapsulePin: (t, o) => cloud.checkTrustRootCapsulePin(t, o),
    warmGossipCdn: (o) => cloud.warmGossipCdn(o),
    slaGatedPromote: (o) => cloud.slaGatedPromote(o),
    clearCrossProjectReceipts: (o) => cloud.clearCrossProjectReceipts(o),
    runAutonomicTrustHeal: (o) => cloud.runAutonomicTrustHeal(o),
    fetchForeignWebKey: (p, k, w) => cloud.fetchForeignWebKey(p, k, w),
    unpackCapsule: (e) => cloud.unpackCapsule(e),
    packCapsule: (d, o) => cloud.packCapsule(d, o),
    skyHandleOf, normalizeSkySuffix, virtualShortHost, virtualGlobalAddressPlan, parseVirtualHost, virtualAddressForPack, capsuleGenomeSelect, migrationBundle, dagIsPure, skydbValidateSchema, quorumDigests,
    mindNormalizeMessages, mindPromptDigest, mindDeltaMessages, mindCompactPrompt, mindEstimateCost,
    mindGovernor, latencyRace, mindShadowGate, telemetryAutopilotPlan, MIND,
    slaScoreFromWorkers, meshPeersFromRegistry, apparentWriteThroughput, cdnMirrorList, addressStackPlan, livingAddressPlan, livingAddressAntiStaticGate,
    liveAddressHealthBadge, snapshotFreshnessGate, shardCanaryPromoteQuorum, continuumEscrowReleasePlan, visitorWorkerBeatPlan,
    domainMeshFailoverPlan, trustRootSkyliveGate, budgetPrefetchGate, federatedAddressStackGate, canarySqlReadOnlyGate,
    seedCallingCardSyncGate, lagTaxQuotePlan,
    healthBadgeRepublishGate, staleSnapshotAutorefreshPlan, shardAttestGossipPlan, continuumEarningsBridgePlan,
    visitorBeatTtlCapPlan, failoverStickyTtlPlan, skyliveDualRootFreezeGate, prefetchReceiptCapPlan,
    fedStackCallingCardGate, canaryReadReplicaFanoutPlan, cardRevokeOnSeedFailPlan, lagTaxQuoteMarketPlan,
    livingSlaDoorCompose, zeroPeerDegradedUiPlan, inferProjectPublishPlan, resolveBuildCommand, findSiteDirWithIndex, walkNativeArtifacts, detectNativeProjectShells, detectAppLoginSurface, clientLoginSyncGate, localDataImportPlan, clientUpdateManifestPlan, inferNativeClientPlan, inferConnectScopePlan, gatewayAccessPackPlan, preferredGatewayHost, buildAccessHtml,    fullAppPublishGate, dedicatedMeshDialPlan,
    orbitalMeanAnomaly, orbitalVisibility, buildOrbitalContactPlan, contactGraphRoute, satelliteDancePlan,
    multipathDiversityScore, dtnBundlePlan, dtnCustodyGate, militaryRatchetStep, spaceMeshManifest,
    networkPathClassFromProbe, antennaInventoryPlan, immortalReplicaTip, antiExtinctionGate, replicaUpdatePlan, selfReplicationPlan,
    merkleTreeFromLeaves, merkleProof, merkleVerify, fullAppMerkleTip, merkleAntiEntropyDiff,
    satLatencyClass, eventualSyncPlan, contactWindowShipGate, versionVectorMerge, replicaTipLagMs,
    brainDigestFromSelfModel, organismMemberManifest, organismQuorumGate, partitionHealPlan, visitorBrainBootstrapPlan, extinctionHazardScore,
    vaultShardPlan, vaultSeal, vaultOpen, antiRevisionGate,
    subMindJobPlan, assignSubMindWorkers, lightDelayContactPlan,
    dialSessionReceiptPlan, multiBridgeRendezvousPlan, visitorWeightGovPlan, stickyProbeHealPlan,
    dualRootLivingQueuePlan, prefetchDistillWarmPlan, replicaLagBoundGate, quoteMarketClearingPlan,
    degradedFullPromotePlan, sideEffectWalShipPlan, cryptoAppContinuumManifest, cryptoAppContinuumGate,
    sovereignPassportPlan, latticeLawGate, organismCensusPlan, ADO, IMMORTAL_MESH, SPACE_MESH, SIOS, ADIS,
    flockMindDigest, flockMergePlan, flockAntennaReachPlan, satellitePathConnectGate,
    spacePresencePlan, spaceAddressPinGate,
    spaceNodeIdFromEndpoint, discoverSpaceCatalog, spaceNeighborGossipPlan, spaceHopReplicationPlan,
    resourceMeterSample, resourceCreditPlan, economySettleGate,
    ensureSpaceAddressPresence: (o) => cloud.ensureSpaceAddressPresence(o),
    discoverSpaceNodes: (o) => cloud.discoverSpaceNodes(o),
    spaceHopReplicate: (o) => cloud.spaceHopReplicate(o),
    runSpaceHopContinuations: (o) => cloud.runSpaceHopContinuations(o),
    addressOnHopReceiptPlan, addressOnHopReceiptChain, spaceCatalogQuorumGate, spaceCatalogQuorumBatch,
    hopVaultShardPlan, hopVaultOpenPlan, adisRecallOnHopPlan,
    hopBandwidthDebitPlan, hopCrosslinkCreditGate, hopChainCreditGate,
    livingAddressRoamSatPlan, flockHopSubMindPlan, vaultDarkWindowHealPlan,
    didSkyDriveHopPinPlan, xiSatEconomyGate,
    roamSkyliveAutopublishGate, quorumTipCutoverGate, hopVaultAntiRevisionGate,
    hopRainStreamPlan, crosslinkBandwidthMarketPlan, xiReceiptOnSatPlan,
    dualRootHopFreezeGate, flockMergeOnRoostsPlan, darkWindowPrefetchCapPlan,
    roamHealthBadgePlan, hopVaultExtinctionHazardPlan, xiSubMindEconomyPlan,
    wasmModulePlan, secureEvalPlan, runtimeInvokePlan,
    skyDrivePutPlan, skyDriveGetPlan, skyDriveHealPlan,
    didDocumentPlan, didResolve, didAuthGate, httpLatticeGatewayPlan, gatewayRewrite,
    embedLattice, cosineSim, vectorLatticePutPlan, vectorLatticeQueryPlan, adisRecallPlan,
    dtaToolPlan, dtaPublishPlan, dtaInvokePlan, unifiedMindFusionPlan,
    slmManifestPlan, slmBootstrapPlan, slmInferPlan, loraDeltaPlan, adisTracePlan, siosPluginManifest,
    SWARM_MESH, swarmNodeManifestPlan, swarmRegisterGate, swarmDiscoverPlan, swarmHandoffPlan,
    swarmSynthesizePlan, swarmSessionDigestPlan, toolLibrarySearchPlan, toolLibraryLazyInjectPlan,
    toolLibraryDeprecatePlan, dtaPublishSwarmPlan,
    enableSIOS: (o) => cloud.enableSIOS(o),
    enableADIS: (o) => cloud.enableADIS(o),
    adisAsk: (i, o) => cloud.adisAsk(i, o),
    zeroTrustBundle,
    mindZeroTrustPackage, capsuleAttest, pickMeshMindWorker, trustMeshQuorum, latencyMindPrice, capsuleCanaryPlan,
    canaryHealGate, canaryRollbackPlan, distillCdnPlan, settlementClearPlan, workerEarningsPlan, seedGossipPlan, trustRootRotatePlan, mindContinuumItem,
    slaPromoteGate, trustRootCapsuleGate, settlementEarningsBridgePlan, autonomicTrustHealPlan, gossipCdnWarmPlan,
    dualRootPromoteGate, promoteEarningsHoldGate, canaryEarningsEscrowPlan, addressBoundCapsuleGate, zeroOriginPayGate,
    verifiedSeedGate, continuumQuorumGate, autonomicPromoteHealPlan, federationCapsulePinGate, shadowPricedMindGate,
    fedSettlementBindGate, dualRootDistillGate, continuumBudgetGate,     warmSeedVerifyGate, multiCanaryShardPlan, genomeLockedSeedGate,
    // Lattice Wave S151–S172 — pure plans + knobs
    latticeWaveDoorsCatalog, bftPropose, bftVote, bftCommit, bftStatusPlan, bftGateAccept, bftThreshold,
    offlineCommitPlan, drainOfflineQueuePlan, offlineQueueStatusPlan, offlineCommitVerify,
    applyOfflineOpPlan, applyOfflineDrainPlan, psiSessionKey, hybridWrap, hybridOpen,
    zkAuthProve, zkAuthVerify, edgeMapReducePlan, stegoEmbedPng, stegoExtractPng,
    covertHttpWrap, covertHttpUnwrap, covertMeshIngestPlan, p2pSyncOncePlan, nodeAgentTickPlan,
    oracleFetchPlan, crdtApplyPlan, crdtGCounterMerge, crdtGCounterValue, vsearchPlan,
    transportEnqueuePlan, transportForwardPlan, domToAxTree, axAction,
    sessionVaultSeal, sessionVaultOpen, metamorphPlan, metamorphDispatch,
    BFT_SYNC, COVERT_MESH, HYBRID_LATTICE, NODE_AGENT, LATTICE_WAVE,
    // 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; },
    // Lattice Wave S151–S172
    bftStatus: () => cloud.bftStatus(),
    bftProposeTip: (d) => cloud.bftProposeTip(d),
    bftVoteOnTip: (v, p) => cloud.bftVoteOnTip(v, p),
    bftCommitTip: (o) => cloud.bftCommitTip(o),
    offlineCommit: (op) => cloud.offlineCommit(op),
    drainOfflineQueue: () => cloud.drainOfflineQueue(),
    offlineQueueStatus: () => cloud.offlineQueueStatus(),
    hybridSessionWrap: (b, o) => cloud.hybridSessionWrap(b, o),
    hybridSessionOpen: (p, o) => cloud.hybridSessionOpen(p, o),
    enablePhysicsLatticeApi: (o) => cloud.enablePhysicsLatticeApi(o),
    latticeDoor: (id, i) => cloud.latticeDoor(id, i),
    skyDrivePut: (p, d, o) => cloud.skyDrivePut(p, d, o),
    skyDriveGet: (p, o) => cloud.skyDriveGet(p, o),
    skyDriveHeal: (o) => cloud.skyDriveHeal(o),
    runtimeInvoke: (b, o) => cloud.runtimeInvoke(b, o),
    sessionVaultPut: (c) => cloud.sessionVaultPut(c),
    sessionVaultGet: (blob) => (blob != null && blob !== '') ? cloud.sessionVaultGet(blob) : cloud.sessionVaultLoad(),
    enablePlugin: (n, o) => cloud.enablePlugin(n, o),
    vaultHeal: (n, o) => cloud.vaultHeal(n, o),
    zkProve: (s, o) => cloud.zkProve(s, o),
    zkVerify: (t, o) => cloud.zkVerify(t, o),
    edgeMapReduce: (j, s, o) => cloud.edgeMapReduce(j, s, o),
    stegoEmbed: (p, o) => cloud.stegoEmbed(p, o),
    stegoExtract: (p, o) => cloud.stegoExtract(p, o),
    p2pSyncOnce: (peer, d) => cloud.p2pSyncOnce(peer, d),
    agentTick: (t) => cloud.agentTick(t),
    oracleGet: (u, e) => cloud.oracleGet(u, e),
    vsearch: (q, d) => cloud.vsearch(q, d),
    vdocPut: (id, t) => cloud.vdocPut(id, t),
    vdocList: () => cloud.vdocList(),
    crdtApply: (op) => cloud.crdtApply(op),
    transportEnqueue: (k, b) => cloud.transportEnqueue(k, b),
    transportForward: (m) => cloud.transportForward(m),
    sessionVaultSave: (c) => cloud.sessionVaultSave(c),
    sessionVaultLoad: () => cloud.sessionVaultLoad(),
    axTree: (h) => cloud.axTree(h),
    axAct: (t, a) => cloud.axAct(t, a),
    sandboxIngest: (b) => cloud.sandboxIngest(b),
    cryptoShred: (b) => cloud.cryptoShred(b),
    metamorph: (s) => cloud.metamorph(s),
    metamorphRuntime: (i, h) => cloud.metamorphRuntime(i, h),
    latticeDoors: () => cloud.latticeDoors(),
    enableLatticeWaveApi: (o) => cloud.enableLatticeWaveApi(o),
    enableSwarmApi: (o) => cloud.enableSwarmApi(o),
    swarmRegister: (p) => cloud.swarmRegister(p),
    swarmDiscover: (q, o) => cloud.swarmDiscover(q, o),
    swarmHandoff: (s, t, o) => cloud.swarmHandoff(s, t, o),
    swarmSynthesize: (a, o) => cloud.swarmSynthesize(a, o),
    swarmAsk: (i, o) => cloud.swarmAsk(i, o),
    swarmMeshTick: () => cloud.swarmMeshTick(),
    toolLibrarySearch: (q, o) => cloud.toolLibrarySearch(q, o),
    toolLibraryPublish: (t, o) => cloud.toolLibraryPublish(t, o),
    toolLibraryLazyInject: (t, o) => cloud.toolLibraryLazyInject(t, o),
    toolLibraryDeprecate: (o) => cloud.toolLibraryDeprecate(o),
  };
}

// חשיפת פונקציות פנימיות לבדיקות בלבד (רץ רק כשמוגדר משתנה סביבה 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, xiExpandPolicyInValue, 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, skyHandleOf, normalizeSkySuffix, virtualShortHost, virtualGlobalAddressPlan, parseVirtualHost, virtualAddressForPack, capsuleGenomeSelect, migrationBundle, dagIsPure, skydbValidateSchema, quorumDigests, mindNormalizeMessages, mindPromptDigest, mindDeltaMessages, mindCompactPrompt, mindEstimateCost, mindGovernor, latencyRace, mindShadowGate, telemetryAutopilotPlan, MIND, slaScoreFromWorkers, meshPeersFromRegistry, apparentWriteThroughput, cdnMirrorList, addressStackPlan, livingAddressPlan, livingAddressAntiStaticGate, liveAddressHealthBadge, snapshotFreshnessGate, shardCanaryPromoteQuorum, continuumEscrowReleasePlan, visitorWorkerBeatPlan, domainMeshFailoverPlan, trustRootSkyliveGate, budgetPrefetchGate, federatedAddressStackGate, canarySqlReadOnlyGate, seedCallingCardSyncGate, lagTaxQuotePlan, healthBadgeRepublishGate, staleSnapshotAutorefreshPlan, shardAttestGossipPlan, continuumEarningsBridgePlan, visitorBeatTtlCapPlan, failoverStickyTtlPlan, skyliveDualRootFreezeGate, prefetchReceiptCapPlan, fedStackCallingCardGate, canaryReadReplicaFanoutPlan, cardRevokeOnSeedFailPlan, lagTaxQuoteMarketPlan, livingSlaDoorCompose, zeroPeerDegradedUiPlan, findSiteDirWithIndex, resolveBuildCommand, inferProjectPublishPlan, inferNativeClientPlan, inferConnectScopePlan, gatewayAccessPackPlan, preferredGatewayHost, buildAccessHtml,  clientUpdateManifestPlan, localDataImportPlan, clientLoginSyncGate, detectAppLoginSurface, detectNativeProjectShells, walkNativeArtifacts, lwwPickRecord, smartMergeValues, smartMergeRecords, isPlainObject, KV_MERGE,  dualWriterLwwMerge, soakRecordsPlan, relayOutageUxPlan, recoveryKitPlan, lostIdRecoveryGate, alertEventPlan, alertThrottleGate, ALERTS, fullAppPublishGate, dedicatedMeshDialPlan, orbitalMeanAnomaly, orbitalVisibility, buildOrbitalContactPlan, contactGraphRoute, satelliteDancePlan, multipathDiversityScore, dtnBundlePlan, dtnCustodyGate, militaryRatchetStep, spaceMeshManifest, networkPathClassFromProbe, antennaInventoryPlan, immortalReplicaTip, antiExtinctionGate, replicaUpdatePlan, selfReplicationPlan, merkleTreeFromLeaves, merkleProof, merkleVerify, fullAppMerkleTip, merkleAntiEntropyDiff, satLatencyClass, eventualSyncPlan, contactWindowShipGate, versionVectorMerge, replicaTipLagMs, brainDigestFromSelfModel, organismMemberManifest, organismQuorumGate, partitionHealPlan, visitorBrainBootstrapPlan, extinctionHazardScore, vaultShardPlan, vaultSeal, vaultOpen, antiRevisionGate, subMindJobPlan, assignSubMindWorkers, lightDelayContactPlan, dialSessionReceiptPlan, multiBridgeRendezvousPlan, visitorWeightGovPlan, stickyProbeHealPlan, dualRootLivingQueuePlan, prefetchDistillWarmPlan, replicaLagBoundGate, quoteMarketClearingPlan, degradedFullPromotePlan, sideEffectWalShipPlan, cryptoAppContinuumManifest, cryptoAppContinuumGate, sovereignPassportPlan, latticeLawGate, organismCensusPlan, flockMindDigest, flockMergePlan, flockAntennaReachPlan, satellitePathConnectGate, spacePresencePlan, spaceAddressPinGate, spaceNodeIdFromEndpoint, discoverSpaceCatalog, spaceNeighborGossipPlan, spaceHopReplicationPlan, addressOnHopReceiptPlan, addressOnHopReceiptChain, spaceCatalogQuorumGate, spaceCatalogQuorumBatch, hopVaultShardPlan, hopVaultOpenPlan, adisRecallOnHopPlan, hopBandwidthDebitPlan, hopCrosslinkCreditGate, hopChainCreditGate, livingAddressRoamSatPlan, flockHopSubMindPlan, vaultDarkWindowHealPlan, didSkyDriveHopPinPlan, xiSatEconomyGate, roamSkyliveAutopublishGate, quorumTipCutoverGate, hopVaultAntiRevisionGate, hopRainStreamPlan, crosslinkBandwidthMarketPlan, xiReceiptOnSatPlan, dualRootHopFreezeGate, flockMergeOnRoostsPlan, darkWindowPrefetchCapPlan, roamHealthBadgePlan, hopVaultExtinctionHazardPlan, xiSubMindEconomyPlan, resourceMeterSample, resourceCreditPlan, economySettleGate, wasmModulePlan, secureEvalPlan, runtimeInvokePlan, skyDrivePutPlan, skyDriveGetPlan, skyDriveHealPlan, didDocumentPlan, didResolve, didAuthGate, httpLatticeGatewayPlan, gatewayRewrite, embedLattice, cosineSim, vectorLatticePutPlan, vectorLatticeQueryPlan, adisRecallPlan, dtaToolPlan, dtaPublishPlan, dtaInvokePlan, unifiedMindFusionPlan, SWARM_MESH, swarmNodeManifestPlan, swarmRegisterGate, swarmDiscoverPlan, swarmHandoffPlan, swarmSynthesizePlan, swarmSessionDigestPlan, toolLibrarySearchPlan, toolLibraryLazyInjectPlan, toolLibraryDeprecatePlan, dtaPublishSwarmPlan, globalAddress, mirrorAddresses, parseDevServerUrlPlan, devServerScriptPlan, projectPublishPipelinePlan, patchCloudKnobFile, slmManifestPlan, slmBootstrapPlan, slmInferPlan, loraDeltaPlan, adisTracePlan, siosPluginManifest, SIOS, ADIS, ADO, zeroTrustBundle, mindZeroTrustPackage, capsuleAttest, pickMeshMindWorker, trustMeshQuorum, latencyMindPrice, capsuleCanaryPlan, canaryHealGate, canaryRollbackPlan, distillCdnPlan, settlementClearPlan, workerEarningsPlan, seedGossipPlan, trustRootRotatePlan, mindContinuumItem, slaPromoteGate, trustRootCapsuleGate, settlementEarningsBridgePlan, autonomicTrustHealPlan, gossipCdnWarmPlan, dualRootPromoteGate, promoteEarningsHoldGate, canaryEarningsEscrowPlan, addressBoundCapsuleGate, zeroOriginPayGate, verifiedSeedGate, continuumQuorumGate, autonomicPromoteHealPlan, federationCapsulePinGate, shadowPricedMindGate, fedSettlementBindGate, dualRootDistillGate, continuumBudgetGate, warmSeedVerifyGate, multiCanaryShardPlan, genomeLockedSeedGate, bftPropose, bftVote, bftCommit, bftStatusPlan, bftGateAccept, bftThreshold, offlineCommitPlan, drainOfflineQueuePlan, offlineQueueStatusPlan, offlineCommitVerify, applyOfflineOpPlan, applyOfflineDrainPlan, psiSessionKey, latticeKemKeygen, latticeKemEncaps, latticeKemDecaps, hybridWrap, hybridOpen, zkAuthProve, zkAuthVerify, edgeMapReducePlan, stegoEmbedPng, stegoExtractPng, covertHttpWrap, covertHttpUnwrap, covertMeshIngestPlan, p2pSignalPlan, p2pSyncOncePlan, nodeAgentTickPlan, nodeAgentMessagePlan, fheKeygen, fheEncSimple, fheAddSimple, fheMulSimple, fheDecSimple, swarmScore, swarmUpdateWeights, swarmPickPath, oracleFetchPlan, crdtApplyPlan, crdtGCounterMerge, crdtGCounterValue, vsearchPlan, transportEnqueuePlan, transportForwardPlan, domToAxTree, axAction, sessionVaultSeal, sessionVaultOpen, sandboxIngest, cryptoShred, metamorphPlan, metamorphDispatch, siblingNeedsPushPlan, bftOracleBridgePlan, fheEdgeJobPlan, stegoDtnCarrierPlan, agentAxLoopPlan, crdtP2pAntiEntropyPlan, latticeWaveDoorsCatalog, LATTICE_WIRED_DOORS, apiAuthCheck, BFT_SYNC, COVERT_MESH, HYBRID_LATTICE, NODE_AGENT, LATTICE_WAVE, schnorrSign, zipEntrySafe, testKeys: () => { ensureId(); deriveKeys(); return { pubkey: keys.pubkey, priv: keys.priv }; }, initHttpApi: () => { process.env.SC_TEST_NO_LIVE = '1'; ensureId(); deriveKeys(); if (!cloud._dbApiEnabled) cloud.enableDbApi({ staticMeta: true }); if (!cloud._latticeWaveApiEnabled) cloud.enableLatticeWaveApi(); if (!cloud._swarmApiEnabled) cloud.enableSwarmApi(); return cloud; }, serveRequest: (req) => cloud._serveRequest(req), healSweepLite: async () => { process.env.SC_TEST_LITE = '1'; try { return await cloud._healSweep(); } finally { delete process.env.SC_TEST_LITE; } } }; }
