להלשכה הדיגיטליתDIGITAL OFFICE · קוד גלוי GitHub
תיעוד · המודל הדקלרטיבי

הכל מוצהר, כלום לא נרשם

איך אפליקציה מקבלת אוטומציות, חיבורים, התראות, כניסה ומסד נתונים בלי לכתוב אותם, איך תלויות מנוהלות היום, ומה קורה כשמכבים רכיב. הדף הזה נגזר מהמניפסטים והקומפיילר האמיתיים, לא מתיאור.

העיקרון

לאפליקציה יש תיקייה אחת וקובץ מניפסט אחד (app.json). זה הדבר היחיד שהפלטפורמה קוראת עליה. אין רישום מרכזי, אין קובץ בסיס שצריך ללמוד את השם שלה, ושער G9 ב-CI נכשל אם קובץ בשכבה נמוכה מזכיר אפליקציה בשכבה גבוהה. להתקין = להוסיף תיקייה. להסיר = למחוק אותה, וכל מה שנגזר ממנה (ניתוב, SSO, אריח, סודות, אוטומציות) נעלם ברינדור הבא.

הקומפיילר (platform/manager/reconcile.py) קורא את כל המניפסטים ואת platform.state.yaml (מה מופעל, ואילו ספקים נבחרו), פותר את גרף היכולות, ומייצר את כל ההרצה לתוך .generated/: שרשרת ה-compose, תוויות Traefik, תבנית Authentik, רישום הסודות, אריחי הקונסולה. Docker מתכנס על התוצאה. שום דבר לא מחווט ביד.

אנטומיה של מניפסט

{
  "id": "indox",                                  // == שם התיקייה, נאכף
  "title": {"he": "מסמכים נכנסים", "en": "INDOX"},
  "layer": 3, "kind": "service", "version": "2.3.0",

  "capabilities": [                               // מה אני מציע לאחרים
    { "name": "indox-api", "required_ops": ["documents", "sync-state", "settings", "tests"] }
  ],
  "use": [                                        // מה אני צריך
    { "capability": "workflow", "strength": "requires" },
    { "capability": "ocr",      "strength": "requires", "ops": ["documentOcr", "documentJsonQuery"] },
    { "capability": "drive",    "strength": "requires", "ops": ["list", "download", "upload", "move"] },
    { "capability": "chat",     "strength": "wants", "after": false, "dependency": "weak" },
    { "capability": "notify",   "strength": "wants", "after": false, "dependency": "weak" }
  ],
  "routing": [{ "host": "incoming-documents", "port": 8080, "auth": "sso",
                "module": { "group": "עסק", "icon": "inbox", "embed": true, "alias": "indox" } }],
  "connections": [{ "id": "gemini-key", "required": true }, { "id": "gmail-app-password", "required": true }],
  "blueprints": { "n8n": "blueprint/n8n", "requires_stdlib": ">=1.0" },   // אוטומציות של האפליקציה
  "data": { "volumes": ["indox_data"], "on_uninstall": "keep" },
  "progress_deadline_seconds": 600
}
שדהמה הוא מצהירמה הקומפיילר מייצר ממנו
capabilitiesחוזה: שם יכולת ורשימת הפעולות שהאפליקציה משרתת (required_ops)רישום ספקים; צרכן שמבקש פעולה שאין לספק נכשל בתכנון, לא בייצור
useיכולות שהאפליקציה צריכה, עם חוזק (למטה), פעולות נדרשות, וסדר הפעלהמשיכת ספקים לגרף, בחירת ספק, סדר עלייה, זיהוי מעגלים
routingשם מארח, פורט, האם מאחורי SSO, נתיבים פטורים, ואריח בקונסולהכל תוויות Traefik (3 נתבים: localhost, HTTP, HTTPS), ספק proxy ב-Authentik, האריח
connectionsאילו סודות האפליקציה צורכת, ואילו היא מוסיפה לקטלוג (contributes)רישום "מי משתמש במה" למישור החיבורים, אשף ההתקנה שואל רק על מה שחסר, מדיניות מתווך ההרשאות
blueprints.n8nתהליכי אוטומציה שהאפליקציה מביאה איתהייבוא בהפעלה, הסרה בכיבוי. שער G13: חייבים גם use: workflow
dataנפחים, ומה קורה להם בהסרה (keep/purge)מסך הגילוי לפני התקנה; אזהרה אדומה על purge
progress_deadline_secondsכמה זמן מותר להיות לא-מוכןתנאי ProgressDeadlineExceeded במקום "עולה" לנצח
brokerהאפליקציה לא מחזיקה סודות בסביבההמשתנים מרוקנים; האפליקציה שוכרת מפתח חד-פעמי ממתווך ההרשאות בזמן ריצה

חוזק תלות: הסמנטיקה המדויקת

חוזקמשמעותמה קורה כשהספק לא מופעל
requiresקשיח. הספק נמשך לגרף ומופעל לפני האפליקציה.הקומפיילר מוסיף אותו אוטומטית ("pulled in as provider"). אם אין ספק בקטלוג בכלל: שגיאת תכנון, שום קונטיינר לא עולה.
requisiteקשיח, וחייב כבר להיות שם. לא נמשך.שגיאת תכנון.
binds_toקשיח, למופע ספציפי אחד.שגיאת תכנון.
wantsרך. משתמשים בו אם קיים. לעולם לא מושך כלום.האפליקציה עולה בלעדיו. באחריות הקוד שלה לעבוד בלי היכולת.
conflictsשתיהן לא יכולות להיות מופעלות יחד.שגיאה שמציינת את שתיהן. אף פעם לא מפילים אחת בשקט.

שני מתגים על סדר, לא על נוכחות: "after": false (צריך את זה, לא צריך שיעלה קודם) ו-"dependency": "weak" (מוריד את קשת הסדר; כך שוברים מעגל). עמימות לא מנוחשת: שני ספקים לאותה יכולת בלי כלל בחירה מפילים את התכנון ומציינים את שניהם. הכלל: bindings ב-platform.state.yaml (בחירת המפעיל, גוברת) → default_provider במניפסט של הצרכן → הספק היחיד.

מה כל אפליקציה מקבלת בלי לכתוב

🌐 ניתוב, דומיין ו-SSL

שורת routing אחת = שלושה נתבים ב-Traefik, תעודת Let's Encrypt, ו-<host>.localhost לפיתוח.

🪪 כניסה אחת

auth: "sso" = Authentik forward-auth. האפליקציה קוראת את המשתמש מכותרות X-authentik-*. בלי קוד אימות.

🧩 אריח בקונסולה

module = אריח ב"האפליקציות שלי", עם embed: true המסך משובץ בתוך הניווט.

🔐 סודות ומתווך

connections[] = הסודות מגיעים מקובץ ההגדרות או מהאשף; עם broker אין סודות עומדים בסביבה בכלל.

⚙️ אוטומציות משלה

blueprints.n8n = תהליכי n8n שמיובאים ומופעלים עם האפליקציה ונמחקים איתה. קונבנציה: תהליך ראוטר אחד שחושף את הפעולות כ-{op, params}.

🔔 התראות ושגיאות

use: notify ו-error-tracking (בדרך כלל wants) = מרכז ההתראות ו-GlitchTip. אם הם כבויים, האפליקציה ממשיכה.

🚦 תנאי בריאות

Resolved / Installed / Started / Ready עם סיבה, הודעה וזמן מעבר. AuthFailed נקרא ממישור החיבורים, לא מנוחש מבריאות הקונטיינר.

📦 קטלוג חתום

התקנה לפי מזהה, כתובת או קובץ, עם חתימת Ed25519, וגילוי לפני התקנה: מפתחות, פורטים, נתיבים, נפחים, עלות.

הגרף האמיתי

מתוך המניפסטים של 27 אפליקציות, כפי שהיו כשהטבלה נוצרה. במאגר הציבורי יש היום יותר מ-40. חובה = requires: הספק נמשך ומופעל קודם. רשות = wants: משתמשים אם קיים. ליד כל יכולת: מי מספק אותה.

אפליקציהמספקתחובהרשותניתוב · כניסהאוטומציותסודות
app-management
ניהול אפליקציות · שכבה 1
ניהול אפליקציות——apps: SSO—0
authentik
משתמשים וכניסה · שכבה 1
זהות (identity) שער כניסה (forward-auth)
בסיס נתונים (sql) ← postgres
—auth: פתוח—4
connections
חיבורים ומפתחות · שכבה 1
סודות (secrets) מופעי חיבור——connections: SSO—0
postgres
בסיס נתונים · שכבה 1
בסיס נתונים (sql)——בלי ניתוב—2
redis
מטמון ותורים · שכבה 1
מטמון ותורים (kv)——בלי ניתוב—1
traefik
שער הכניסה · שכבה 1
נתב קצה—
זהות (identity) ← authentik
traefik: SSO—1
any-concurrency
כל מקביל · שכבה 2
מדדי מקביליות
בסיס נתונים (sql) ← postgres
מטמון ותורים (kv) ← redis
—any-concurrency: SSO—2
console
המרכז · שכבה 2
מעטפת (shell)
סודות (secrets) ← connections
זהות (identity) ← authentik
פנקס (ledger) ← apitools
התראות (notify) ← notifications
ספריית ממשק ← bina-ui
console: SSO—0
glitchtip
מעקב שגיאות · שכבה 2
מעקב שגיאות
בסיס נתונים (sql) ← postgres
מטמון ותורים (kv) ← redis
—glitchtip: פתוח—4
machine
מכונה · שכבה 2
מדדי מארח——machine: SSO—0
n8n
אוטומציות · שכבה 2
אוטומציות (workflow) ספריית אוטומציות
בסיס נתונים (sql) ← postgres
מטמון ותורים (kv) ← redis
סודות (secrets) ← connections
מעקב שגיאות ← glitchtip
n8n: פתוח✓7
notifications
התראות · שכבה 2
התראות (notify)—
מעקב שגיאות ← glitchtip
alerts-svc: SSO—1
apitools
כלי מסמכים · שכבה 3
OCR פנקס (ledger)
בסיס נתונים (sql) ← postgres
סודות (secrets) ← connections
—apitools: פתוח—6
browser42
דפדפן · שכבה 3
דפדפן——browser42: SSO—2
chat
צ׳אט · שכבה 3
צ'אט
אוטומציות (workflow) ← n8n
סודות (secrets) ← connections
התראות (notify) ← notifications
מסמכים נכנסים ← indox
chat: SSO✓4
drive
דרייב · שכבה 3
דרייב
אוטומציות (workflow) ← n8n
בסיס נתונים (sql) ← postgres
סודות (secrets) ← connections
אחסון אובייקטים ← supabase
—drive: SSO✓2 · broker
google-workspace-agent
סוכן Google Workspace · שכבה 3
—
אוטומציות (workflow) ← n8n
סודות (secrets) ← connections
—בלי ניתוב✓0
hello-bina
שלום בינה · שכבה 3
———hello-bina: SSO—0
indox
מסמכים נכנסים · שכבה 3
מסמכים נכנסים
אוטומציות (workflow) ← n8n
בסיס נתונים (sql) ← postgres
סודות (secrets) ← connections
מטמון ותורים (kv) ← redis
REST לנתונים (data-api) ← supabase
OCR ← apitools
דרייב ← drive
מייל ← mail
צ'אט ← chat
התראות (notify) ← notifications
incoming-documents: SSO✓6
mail
מייל · שכבה 3
מייל שרת מייל מקומי
אוטומציות (workflow) ← n8n
סודות (secrets) ← connections
—mailapi: SSO
mail: SSO
✓2
my-docs
מסמכים שלי · שכבה 3
—
בסיס נתונים (sql) ← postgres
זהות (identity) ← authentik
פנקס (ledger) ← apitools
ספריית ממשק ← bina-ui
my-docs: SSO—1
ollama
מודלים מקומיים · שכבה 3
מודל שפה מקומי——ollama: SSO—0
openhands
סוכן קוד · שכבה 3
סוכן קוד
סודות (secrets) ← connections
מודל שפה מקומי ← ollama
openhands: SSO—2
portainer
מכולות · שכבה 3
———portainer: פתוח—1
redisinsight
מפקח Redis · שכבה 3
—
מטמון ותורים (kv) ← redis
—redisinsight: SSO—1
supabase
סופאבייס · שכבה 3
אחסון אובייקטים REST לנתונים (data-api)
בסיס נתונים (sql) ← postgres
—supabase: פתוח
supabase-studio: SSO
—4

מה קורה כשמכבים משהו, היום

  • שכבה 1 לא נכבית. app-management, traefik, postgres, redis, authentik, connections מסומנים system: הקונסולה לא מציעה לכבות אותם. זו ההגדרה הנוכחית, לא חוק טבע.
  • מכבים ספק של requires: הקומפיילר מושך אותו חזרה (הוא נדרש), או, אם הוצא מהקטלוג, מפיל את התכנון בשגיאה שמציינת מי צריך מה. אף אפליקציה לא עולה חצי.
  • מכבים ספק של wants: הצרכנים עולים בלעדיו. למשל: כיבוי GlitchTip משאיר את n8n, ההתראות והצ'אט עובדים; כיבוי הצ'אט משאיר את מסמכים נכנסים עובד בלי ערוץ וואטסאפ.
  • Authentik נופל (לא כבוי, נופל): כל נתיב עם auth: sso מחזיר 502 שמציין את שירות האימות. זה מכוון: ה-middleware מוגדר בקובץ ולא בתוויות, כדי ש-Traefik לא יזרוק את הנתבים ויחזיר 404 שנראה כמו "האפליקציה לא מותקנת" (נמדד: 3,718 דחיות נתבים בחצי שעה לפני התיקון). נתיבים עם auth: none (apitools, supabase API, glitchtip, n8n webhooks) ממשיכים לענות.
  • מסמכים נכנסים היום: תלוי קשיח ב-workflow (n8n), sql, secrets, kv, data-api, ocr (apitools), drive ו-mail. רך: chat, notify. כלומר כיבוי הדרייב או המייל היום מונע ממסמכים נכנסים לעלות, למרות שהם רק מקורות סנכרון.

היעד: לאן זה הולך

הכוונה המוצהרת של הפרויקט: כל רכיב שאינו חיוני לאפליקציה הוא wants, ואם הוא כבוי האפליקציה עובדת בלעדיו. שער הכניסה הוא שער, לא תלות. שלושה שינויים סוגרים את הפער:

  1. זהות כשער שאפשר לכבות. היום identity הוא ספק system. היעד: כש-authentik כבוי, הקומפיילר מרנדר את נתיבי ה-sso בלי ה-middleware ומדפיס אזהרה אחת גדולה ("כל המערכת פתוחה"), במקום 502. אפליקציה שמצהירה identity: requires (היום: המרכז ומסמכים שלי, כי הן קוראות את המשתמש מהכותרות) תפיל את התכנון בשגיאה ברורה, כי בשבילן זה לא שער אלא תלות. כל השאר עולות פתוחות. זה שינוי בקומפיילר בלבד; המניפסטים כבר אומרים מי צריך זהות באמת.
  2. מסמכים נכנסים: דרייב ומייל ל-wants. הסנכרון בודק אילו מקורות זמינים ומדלג על מה שכבוי, כמו שהוא כבר עושה לצ'אט. n8n ו-OCR נשארים requires, כי בלעדיהם אין צנרת.
  3. ספריית אוטומציות משותפת כיכולת. requires_stdlib: ">=1.0" כבר קיים במניפסט של מסמכים נכנסים; היעד הוא שגרסת יכולת תיאכף בתכנון (capabilities[].version, צרכן ^1.3), כך ששבירת חוזה בין ספק לצרכן תיתפס בקומפילציה ולא בזמן ריצה.
הכלל שמאחורי הכל: אם משהו נשבר בשקט, מוסיפים שער. כל אחד מהשערים ב-CI קיים כי פעם משהו דיווח "בריא" והיה שבור.

השערים שאוכפים את המודל

שערמה הוא מבטיח
G5 ownershipשום קובץ לא חי מחוץ לאפליקציה שבבעלותו
G6 authנתב פתוח לא חולק כלל ניתוב עם נתב מוגן
G7 composeאף אחד לא מריץ docker compose ישירות (זה no-op שקט)
G9 layeringשכבה נמוכה לא מזכירה אפליקציה משכבה גבוהה
G13 blueprintsאפליקציה עם תהליכי n8n מצהירה use: workflow
G15 image pinsתמונות צד שלישי מוצמדות לגרסה
G16 conditionsתנאי הבריאות מדווחים אמת, לא על עצמם
G17 resolverהקומפיילר לא מנחש ספק
G18 healthcheckבדיקת הבריאות משתמשת בבינארי שקיים בתמונה
G19 sub-workflowsקריאה לתת-תהליך לא מעבירה null
G20 sso providersספקי SSO נגזרים מהמניפסטים, לא כתובים ביד
G21 route claimsשתי אפליקציות לא תופסות אותו מארח

ואחרי כולם: scripts/fresh-machine-check.sh, שמשכפל למכונה ריקה בלי .env ובלי נפחים ומוכיח שהכל מתקמפל מאפס. "עובד אצלי" לא נחשב.

← חזרה לתיעוד · האפליקציות · היכולות