Skip to content
Jay-Bee-CodePublic

About

Production-ready, offline-first B2B document archiving system for government/enterprise environments. Built with Tauri 2, Rust, React, TypeScript & SQLCipher with P2P LAN sync.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

نظام الأرشفة الحكومي (Government Archive System) — B2B Offline-First

نظام أرشفة إلكتروني للمؤسسات الحكومية، مبني بـ Tauri، يعمل بشكل كامل بدون اتصال بالإنترنت (Offline-First)، مع إمكانية المزامنة بين الأجهزة عبر الشبكة المحلية (LAN).


1. نظرة عامة على المشروع

1.1 المشكلة

المؤسسات الحكومية تتعامل مع كميات كبيرة من الوثائق الورقية والإلكترونية، وغالبًا ما تعاني من:

  • انقطاع الإنترنت أو قيود أمنية تمنع استخدام حلول سحابية (Cloud)
  • غياب بحث موحّد وسريع عبر الأرشيف
  • غياب سجل تدقيق (Audit Trail) موثوق لمن يطّلع على ماذا ومتى
  • الحاجة لمشاركة الوثائق بين عدة مصالح/مكاتب داخل نفس المبنى دون خادم مركزي بعيد

1.2 الحل

تطبيق سطح مكتب (Desktop App) خفيف يعمل على كل جهاز كـ "عقدة" (Node) مستقلة، تخزّن أرشيفها محليًا، وتتزامن تلقائيًا مع الأجهزة الأخرى المتصلة بنفس الشبكة المحلية — دون الحاجة لخادم مركزي أو اتصال إنترنت.

1.3 الجمهور المستهدف

  • الدوائر البلدية والولائية
  • المحاكم والموثقين
  • المؤسسات الصحية العمومية
  • أي هيئة حكومية بحاجة لأرشفة رقمية آمنة ومحلية

2. المبادئ المعمارية

المبدأ التفسير
Offline-First التطبيق يعمل بكامل وظائفه بدون إنترنت؛ المزامنة اختيارية وتلقائية عند توفر الشبكة المحلية
Local-First Data كل جهاز يملك نسخة كاملة (أو جزئية حسب الصلاحيات) من البيانات محليًا
Zero External Dependency لا اعتماد على أي خدمة سحابية خارجية؛ كل شيء يعمل داخل حدود المؤسسة
Security by Design تشفير، توقيع، وصلاحيات من اليوم الأول وليس إضافة لاحقة
Auditability كل عملية (إضافة، تعديل، حذف، فتح وثيقة) مسجّلة بشكل غير قابل للتعديل

3. المعمارية العامة (High-Level Architecture)

┌─────────────────────────────────────────────────────────────┐
│                     جهاز المستخدم (Node)                      │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                    Tauri Application                   │  │
│  │  ┌─────────────────┐      ┌──────────────────────────┐│  │
│  │  │   Frontend       │      │   Rust Backend (Core)     ││  │
│  │  │   (React/Svelte) │◄────►│  - Commands (IPC)         ││  │
│  │  │   - UI/UX Layer  │      │  - Sync Engine             ││  │
│  │  │   - State Mgmt   │      │  - Crypto Layer            ││  │
│  │  └─────────────────┘      │  - File Storage Manager    ││  │
│  │                            │  - Search Engine (FTS5)    ││  │
│  │                            └──────────┬─────────────────┘│  │
│  │                                       │                   │  │
│  │                            ┌──────────▼─────────────────┐│  │
│  │                            │   SQLite (SQLCipher)        ││  │
│  │                            │   + Local File Vault        ││  │
│  │                            └──────────────────────────────┘│
│  └───────────────────────────────────────────────────────┘  │
└───────────────────────────┬─────────────────────────────────┘
                             │
                   mDNS Discovery + WebSocket
                             │
┌───────────────────────────▼─────────────────────────────────┐
│              أجهزة أخرى على نفس الشبكة المحلية (LAN)           │
│         (نفس البنية: Node مستقل بأرشيفه الخاص)                │
└───────────────────────────────────────────────────────────────┘

4. المكدس التقني (Tech Stack)

4.1 الطبقة الأساسية (Shell)

  • Tauri 2.x — إطار العمل الأساسي (Rust + WebView)
  • Rust — المنطق الخلفي، التشفير، المزامنة، إدارة الملفات

4.2 الواجهة الأمامية (Frontend)

  • React 18 + TypeScript (أو Svelte 5 كبديل أخف)
  • Tailwind CSS 4 — للتصميم
  • shadcn/ui أو Radix UI — مكونات UI قابلة للوصول (Accessible)
  • Zustand — إدارة الحالة (أخف من Redux)
  • TanStack Query — لإدارة عمليات القراءة/الكتابة غير المتزامنة مع Rust backend
  • react-pdf أو pdf.js — لعرض الوثائق داخل التطبيق

4.3 قاعدة البيانات والتخزين

  • SQLite + SQLCipher — قاعدة بيانات مشفّرة محليًا
  • FTS5 — للبحث النصي الكامل داخل محتوى الوثائق والميتاداتا
  • File Vault — مجلد منظّم لتخزين الملفات الفعلية، مسمّاة بـ hash (SHA-256) لتفادي التكرار

4.4 المزامنة عبر الشبكة المحلية

  • mDNS/Zeroconf (mdns-sd crate) — لاكتشاف الأجهزة تلقائيًا بدون إعداد IP يدوي
  • WebSocket — قناة الاتصال بين الأجهزة للمزامنة
  • CRDT خفيف / Vector Clocks — لحل التعارضات (الأرشيف غالبًا Append-Only فيقلّل هذا من التعقيد)

4.5 الأمان

  • AES-256 — تشفير الملفات في الخزنة المحلية
  • Ed25519 — توقيع رقمي للوثائق عند الإدخال (ضمان السلامة)
  • Argon2 — تجزئة كلمات المرور
  • RBAC (Role-Based Access Control) — أدوار: مدير النظام، موظف أرشفة، مراجع، قراءة فقط

4.6 OCR (اختياري)

  • Tesseract OCR عبر binding Rust — لتحويل الوثائق الممسوحة ضوئيًا إلى نص قابل للبحث

5. مخطط قاعدة البيانات (Database Schema — مبسّط)

-- المستخدمون
CREATE TABLE users (
    id TEXT PRIMARY KEY,           -- UUID
    username TEXT UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    role TEXT NOT NULL,            -- admin | archivist | reviewer | viewer
    full_name TEXT,
    department TEXT,
    created_at INTEGER NOT NULL,
    is_active INTEGER DEFAULT 1
);

-- الوثائق
CREATE TABLE documents (
    id TEXT PRIMARY KEY,           -- UUID
    title TEXT NOT NULL,
    category TEXT,
    file_hash TEXT NOT NULL,       -- SHA-256 للملف الفعلي
    file_path TEXT NOT NULL,       -- المسار داخل الـ Vault
    file_size INTEGER,
    mime_type TEXT,
    ocr_text TEXT,                 -- النص المستخرج (للبحث)
    signature TEXT,                -- التوقيع الرقمي
    created_by TEXT REFERENCES users(id),
    created_at INTEGER NOT NULL,
    updated_at INTEGER,
    origin_node_id TEXT NOT NULL,  -- الجهاز الذي أُدخلت منه الوثيقة أصلًا
    is_deleted INTEGER DEFAULT 0
);

-- فهرسة البحث النصي الكامل
CREATE VIRTUAL TABLE documents_fts USING fts5(
    title, ocr_text, category, content=documents, content_rowid=rowid
);

-- سجل التدقيق (Audit Log) — غير قابل للتعديل
CREATE TABLE audit_log (
    id TEXT PRIMARY KEY,
    document_id TEXT,
    user_id TEXT,
    action TEXT NOT NULL,          -- created | viewed | updated | deleted | synced
    timestamp INTEGER NOT NULL,
    node_id TEXT NOT NULL,
    metadata TEXT                  -- JSON إضافي
);

-- سجل المزامنة (Sync Log / Event Log)
CREATE TABLE sync_events (
    id TEXT PRIMARY KEY,
    entity_type TEXT NOT NULL,     -- document | user | category
    entity_id TEXT NOT NULL,
    operation TEXT NOT NULL,       -- insert | update | delete
    vector_clock TEXT NOT NULL,    -- JSON: {node_id: counter}
    payload TEXT,                  -- JSON بالتغييرات
    origin_node_id TEXT NOT NULL,
    created_at INTEGER NOT NULL,
    synced INTEGER DEFAULT 0
);

-- الأجهزة المعروفة (Nodes)
CREATE TABLE known_nodes (
    node_id TEXT PRIMARY KEY,
    node_name TEXT,
    last_ip TEXT,
    last_seen INTEGER,
    trusted INTEGER DEFAULT 0,     -- يتطلب موافقة يدوية أول مرة
    public_key TEXT
);

6. آلية المزامنة (Sync Engine) — التفصيل

6.1 الاكتشاف (Discovery)

  1. عند تشغيل التطبيق، يبث الجهاز إعلان mDNS بمعرّفه (node_id) واسمه على الشبكة المحلية
  2. الأجهزة الأخرى تكتشفه تلقائيًا وتضيفه لقائمة known_nodes (كـ "غير موثوق" حتى موافقة يدوية)
  3. أول اتصال يتطلب مصادقة يدوية (مثل إقران Bluetooth) — تبادل مفاتيح عامة (Public Key Exchange) لتفادي انتحال جهاز

6.2 دورة المزامنة (Sync Cycle)

Node A                              Node B
  │                                    │
  ├── WebSocket Connect ──────────────►│
  │                                    │
  ├── إرسال آخر vector_clock معروف ───►│
  │                                    │
  │◄── إرسال sync_events الناقصة ──────┤
  │                                    │
  ├── تطبيق التغييرات محليًا           │
  ├── حل التعارضات (إن وجدت)          │
  │                                    │
  ├── إرسال sync_events الخاصة به ────►│
  │                                    │
  │◄── تأكيد الاستلام ─────────────────┤

6.3 حل التعارضات (Conflict Resolution)

  • الحالة الشائعة (99%): الأرشيف Append-Only → لا تعارض، فقط دمج
  • حالة التعديل المتزامن: استخدام Vector Clocks لتحديد الأسبقية السببية، وفي حال التعارض الحقيقي → Last-Write-Wins مع الاحتفاظ بالنسخة القديمة في audit_log (لا حذف نهائي أبدًا)

6.4 المزامنة الجزئية (Selective Sync)

حسب صلاحيات كل مستخدم/مصلحة، يمكن تحديد أي الفئات (category) تُزامَن مع أي جهاز — مهم لأن مصلحة معيّنة قد لا يحق لها رؤية أرشيف مصلحة أخرى.


7. تصميم واجهة المستخدم / تجربة المستخدم (UI/UX Design)

7.1 الفلسفة التصميمية

نظام حكومي = ثقة، وضوح، جدّية. التصميم يجب أن يبتعد عن الطابع "الستارت أب" اللافت، ويقترب من الطابع المؤسساتي الموثوق مع لمسة عصرية بسيطة.

الكلمات المفتاحية: هادئ، منظّم، عالي التباين، خالٍ من الزخرفة الزائدة.

7.2 نظام الألوان (Color System)

الاستخدام اللون القيمة
أساسي (Primary) أزرق مؤسساتي عميق #1A3A5C
ثانوي (Accent) أزرق فولاذي #3B6EA5
نجاح أخضر هادئ #2E7D5B
تحذير كهرماني #B8860B
خطر/حذف أحمر مطفأ #B23A3A
خلفية (فاتح) رمادي فاتح جدًا #F7F8FA
خلفية (داكن) كحلي داكن جدًا #0F1B2B
نص أساسي رمادي كحلي #1E2A38
نص ثانوي رمادي متوسط #5A6B7B
حدود رمادي فاتح #DDE3EA

يُفضَّل دعم Dark Mode كامل — بيئات العمل الحكومية غالبًا ذات إضاءة مختلفة (مكاتب مسائية، غرف أرشيف).

7.3 الخطوط (Typography)

  • العربية: IBM Plex Sans Arabic أو Noto Kufi Arabic — وضوح عالٍ في الأحجام الصغيرة
  • اللاتينية: Inter أو IBM Plex Sans
  • دعم RTL كامل من الأساس (ليس إضافة لاحقة) — استخدام dir="rtl" على مستوى التطبيق مع Tailwind's logical properties (ms-, me- بدل ml-, mr-)

التدرج الهرمي:

العنصر الحجم الوزن
عنوان رئيسي 28px Bold
عنوان قسم 20px SemiBold
نص أساسي 15px Regular
نص ثانوي/تلميحات 13px Regular

7.4 مبادئ التخطيط (Layout)

  • Sidebar ثابت (يمين الشاشة في RTL) — تصنيفات الأرشيف، الوصول السريع
  • شريط علوي (Topbar) — بحث شامل + إشعارات المزامنة + حالة الاتصال بالشبكة المحلية (مؤشر بصري: متصل/غير متصل/يتزامن)
  • منطقة المحتوى الرئيسية — قائمة/شبكة وثائق قابلة للتبديل (List/Grid View)
  • لوحة معاينة جانبية — معاينة سريعة للوثيقة دون فتحها بالكامل

7.5 مكوّنات رئيسية

  1. بطاقة الوثيقة (Document Card) — أيقونة النوع (PDF/صورة/Word)، العنوان، الفئة، تاريخ الإضافة، شارة "متزامن ✓" أو "محلي فقط ⏳"
  2. مؤشر حالة المزامنة (Sync Status Indicator) — أيقونة ثابتة في الزاوية توضح: متصل بعدد X من الأجهزة، آخر مزامنة، تعارضات معلّقة (إن وجدت)
  3. نافذة البحث الشامل (Global Search / Command Palette) — اختصار Ctrl+K يفتح بحثًا فوريًا عبر العناوين والمحتوى (FTS5) والميتاداتا
  4. سجل التدقيق المرئي (Audit Timeline) — عرض زمني بصري لكل من فتح/عدّل الوثيقة
  5. شارات الصلاحيات (Permission Badges) — توضيح بصري لمن يمكنه رؤية/تعديل كل فئة أرشيف

7.6 حالات خاصة يجب تصميمها

  • وضع عدم الاتصال بالكامل — رسالة واضحة غير مقلقة: "تعمل محليًا — سيتم المزامنة عند توفر الشبكة"
  • تعارض مزامنة — واجهة مقارنة بسيطة (نسخة أ / نسخة ب) لاختيار المستخدم المخوّل
  • جهاز غير موثوق يحاول الاتصال — نافذة تأكيد صريحة (مثل إقران Bluetooth) لمنع الانتحال

7.7 إمكانية الوصول (Accessibility)

  • تباين ألوان يحقق WCAG AA كحد أدنى
  • دعم كامل للتنقل بلوحة المفاتيح
  • أحجام خط قابلة للتكبير من الإعدادات (مهم لموظفين بأعمار متفاوتة)

8. هيكلة المشروع (Project Structure)

govt-archive-system/
├── src-tauri/                    # Rust Backend
│   ├── src/
│   │   ├── main.rs
│   │   ├── commands/             # Tauri IPC Commands
│   │   │   ├── documents.rs
│   │   │   ├── auth.rs
│   │   │   └── sync.rs
│   │   ├── db/
│   │   │   ├── schema.rs
│   │   │   └── migrations/
│   │   ├── sync/
│   │   │   ├── discovery.rs      # mDNS
│   │   │   ├── engine.rs         # Sync logic
│   │   │   └── conflict.rs       # Conflict resolution
│   │   ├── crypto/
│   │   │   ├── encryption.rs
│   │   │   └── signing.rs
│   │   ├── storage/
│   │   │   └── vault.rs          # File management
│   │   └── search/
│   │       └── fts.rs
│   ├── Cargo.toml
│   └── tauri.conf.json
│
├── src/                          # Frontend
│   ├── components/
│   │   ├── ui/                   # shadcn components
│   │   ├── DocumentCard.tsx
│   │   ├── SyncIndicator.tsx
│   │   ├── SearchPalette.tsx
│   │   └── AuditTimeline.tsx
│   ├── pages/
│   │   ├── Dashboard.tsx
│   │   ├── Archive.tsx
│   │   ├── Settings.tsx
│   │   └── Login.tsx
│   ├── stores/                   # Zustand
│   ├── hooks/
│   ├── lib/
│   │   └── tauri-api.ts          # IPC wrappers
│   ├── styles/
│   │   └── theme.css
│   └── App.tsx
│
├── docs/
│   ├── architecture.md
│   └── api.md
│
└── README.md

9. مراحل التطوير المقترحة (Roadmap)

المرحلة المحتوى التقدير الزمني
1. الأساس إعداد Tauri + SQLite + CRUD أساسي للوثائق 2-3 أسابيع
2. البحث والتصنيف FTS5، الفئات، الفلترة 1-2 أسبوع
3. الأمان تشفير، مصادقة، RBAC 2 أسبوع
4. المزامنة mDNS Discovery + WebSocket Sync Engine 3-4 أسابيع
5. حل التعارضات Vector Clocks + واجهة حل التعارض 1-2 أسبوع
6. OCR (اختياري) دمج Tesseract 1-2 أسبوع
7. صقل UI/UX تطبيق نظام التصميم كاملًا، Dark Mode 2 أسبوع
8. الاختبار والتوثيق اختبارات، توثيق API، دليل نشر 2 أسبوع

10. اقتراحات وتحسينات معمارية متقدمة (Advanced Architectural Enhancements)

10.1 تشفير الملفات في الخزنة (File Vault Encryption)

تشفير SQLCipher يحمي قاعدة البيانات (الميتاداتا والفهارس)، لكنه لا يغطي الملفات الثنائية المخزَّنة في الـ Vault. يُضاف تشفير مستقل لكل ملف:

  • الخوارزمية: AES-256-GCM (تشفير + مصادقة في عملية واحدة — أي تلاعب بالملف يُكتشف عند فك التشفير)
  • اشتقاق المفتاح: مفتاح رئيسي (Master Key) يُخزَّن في OS Keyring (Windows Credential Manager / macOS Keychain / Secret Service API على Linux)، ويُشتق منه مفتاح تشفير لكل ملف عبر Argon2id — هذا يفصل "مفتاح فتح الجلسة" عن "مفاتيح تشفير الملفات" الفعلية
  • Key Wrapping: المفتاح الرئيسي يُغلَّف (wrapped) بكلمة مرور المستخدم، مما يسمح بتدوير كلمة المرور دون إعادة تشفير كامل الأرشيف
  • كل ملف مشفّر يحمل رأسًا (header) يتضمن Nonce فريدًا + إصدار خوارزمية التشفير (لدعم الترقية المستقبلية)

10.2 إعادة المزامنة الفعّالة عبر Merkle Trees

تبادل سجل sync_events بالكامل عند كل اتصال يصبح مكلفًا مع نمو الأرشيف. الحل: شجرة تجزئة (Merkle Tree) لكل category:

  • كل عقدة تبني شجرة تجزئة فوق أحداث المزامنة المرتّبة زمنيًا لكل فئة أرشيف
  • عند الاتصال بين عقدتين، يتم تبادل جذر الشجرة (root hash) فقط
    • تطابق الجذور → لا حاجة لأي مزامنة لتلك الفئة
    • عدم التطابق → نزول تدريجي عبر فروع الشجرة لعزل الأجزاء المختلفة فقط
  • هذا يقلّل تعقيد كل جولة مزامنة من O(n) إلى O(log n)، ويجعل التعافي من انقطاعات LAN المفاجئة أسرع بكثير خاصة مع أرشيفات كبيرة

10.3 إدارة النسخ الاحتياطي للمفاتيح (Key Backup & Recovery)

في نموذج Multi-Node بدون خادم مركزي، فقدان جهاز واحد قبل أول مزامنة يعني فقدان دائم للبيانات المشفّرة عليه. يُضاف:

  • مفتاح طوارئ (Recovery Key) يُصدَّر عند الإعداد الأولي لكل عقدة، يسمح بفك تشفير قاعدة SQLCipher دون كلمة مرور المستخدم الأصلية
  • Shamir's Secret Sharing بدل مفتاح استرجاع واحد: تقسيم مفتاح الطوارئ إلى عدة أجزاء (مثلاً 3 من 5) تُوزَّع على مسؤولين مختلفين داخل المؤسسة — لا أحد يملك القدرة على الاسترجاع منفردًا، مما يمنع التحكم الفردي الكامل ببيانات حساسة
  • توثيق إجراء الاسترجاع كخطوة إدارية رسمية (يتطلب حضور عدد أدنى من حاملي الأجزاء) وتسجيله في audit_log

11. ملاحظات تشغيلية للقطاع الحكومي

  • النسخ الاحتياطي: خيار تصدير الأرشيف الكامل مشفّرًا إلى وسيط خارجي (USB) دوريًا
  • التوزيع بدون إنترنت: حزمة تثبيت (Installer) موحّدة توزَّع عبر USB أو شبكة داخلية دون الحاجة لمتجر تطبيقات
  • التحديثات: آلية تحديث محلية (بدون Auto-Update عبر الإنترنت) — تحديث عبر ملف موقَّع رقميًا يُوزَّع يدويًا أو عبر عقدة رئيسية داخل الشبكة

12. دراسة حالة (Study Case): بناء نظام أرشفة موزَّع من الصفر

قسم موجَّه للمشاركة والتوثيق — يلخّص الرحلة الفعلية لبناء هذا النظام: القرارات، العقبات الحقيقية التي واجهناها، وكيف حُلَّت. كل رقم هنا مقيس من المشروع نفسه، وليس تقديرًا.

12.1 لقطة سريعة

الهدف نظام أرشفة B2B لمؤسسات حكومية جزائرية، يعمل بلا إنترنت، ويتزامن عبر الشبكة المحلية فقط
المكدس Tauri 2 (Rust) + React/TypeScript + SQLite (SQLCipher)
حجم الـ backend ~3,886 سطر Rust عبر 22 ملفًا (مصادقة، وثائق، فئات، أقسام، أنواع، احتفاظ/إتلاف، أرشيف ورقي، مراسلة، إحالة، تشفير، QR، مزامنة بـ5 وحدات فرعية، OCR)
حجم الواجهة ~3,344 سطر TypeScript/React عبر 25 مكوّنًا وصفحة
الاختبارات 33 اختبار تكامل حقيقي (~1,177 سطر) — مزامنة كاملة بين عقدتين عبر WebSocket فعلي، اكتشاف mDNS، OCR بثلاث لغات، إتلاف يُزامَن ويحذف الملف على الجهازين، ودورة ذهاب وإياب تتحقق من تطابق الصف في 24 عمودًا
المراحل 13 مرحلة (أساس → فئات → أمان → مزامنة → تعارضات → OCR → هيكل تنظيمي/ترقيم → احتفاظ وإتلاف/أرشيف ورقي → مراسلة/إحالة → إعادة هيكلة الواجهة)، صُمِّمت لتُبنى بترتيب مقصود يمنع إعادة العمل

12.2 القرارات المعمارية التي شكّلت المشروع

1. Offline-first حقيقي، لا "PWA بذاكرة تخزين مؤقت". كل عقدة (جهاز) تملك نسخة كاملة من قاعدة البيانات ومحرك مزامنة خاص بها؛ لا خادم مركزي، ولا نقطة فشل واحدة. القرار كلّف تعقيدًا إضافيًا (سجل أحداث، متجهات إصدار، حل تعارضات) لم يكن ليُحتاج في نموذج عميل-خادم تقليدي — لكنه الشرط الذي بدأ منه المشروع أصلًا.

2. تشفير الملفات مستقل عن تشفير القاعدة. SQLCipher يحمي الميتاداتا، لكن الملفات الثنائية تُشفَّر بشكل منفصل بـ AES-256-GCM. الفائدة الفعلية ظهرت في Phase 4: عند مزامنة ملف بين جهازين، كل جهاز يشفّره بمفتاحه الخاص عند الاستلام، لا بنسخة موحّدة — فاختراق جهاز واحد لا يكشف أرشيف الشبكة كلها.

3. المزامنة كعملية فرعية من التشفير الموجود أصلًا، لا طبقة منفصلة. بما أن كل حدث في sync_events يحمل حالة الكيان كاملة (Upsert)، حل التعارضات اختُزل إلى قاعدة واحدة بسيطة (Last-Writer-Wins بمقياس Lamport) بدل بناء CRDT مخصَّص — قرار وازن التعقيد بالحاجة الفعلية، لا بالحل "الأكمل نظريًا".

4. OCR كعملية فرعية (subprocess) لا FFI. التفصيل الكامل في القسم 12.3 أدناه — أهم قرار وحيد وفّر أكبر قدر من وقت التطوير.

12.3 عقبات حقيقية وكيف حُلَّت

هذا الجدول من سجل التطوير الفعلي، لا مثالًا افتراضيًا:

العقبة السبب الجذري الحل
EBUSY: resource busy or locked من Vite على ويندوز Vite يراقب كامل المشروع افتراضيًا، بما فيه src-tauri/target — ملفات .exe مؤقتة تُقفَل أثناء ترجمة Rust استثناء src-tauri/** من مراقب ملفات Vite صراحةً (watch.ignored)
no method named path_resolver الكود كُتب بنمط Tauri 1؛ Tauri 2 أزالت path_resolver() لصالح app.path() عبر trait Manager يجب استيراده استبدال كل استدعاء + use tauri::Manager;
فشل بناء Windows Resource File نسيان أيقونات .ico/.icns إلزامية، وملف capabilities/default.json المطلوب في نظام صلاحيات Tauri 2 توليد أيقونات placeholder ببرمجية، وإضافة ملف الصلاحيات الافتراضي
perl not found عند تفعيل SQLCipher bundled-sqlcipher-vendored-openssl يبني OpenSSL من المصدر، وهذا يتطلب Perl غير موجود افتراضيًا على ويندوز توثيق تثبيت Strawberry Perl كخطوة بناء مطلوبة مرة واحدة
بناء OpenSSL يبدو "عالقًا" لعشرات الدقائق خطوة ترجمة وحيدة ثقيلة بلا مؤشر تقدّم تدريجي على ويندوز (nmake بخيط شبه واحد) التحقق من نشاط cl.exe/nmake عبر Get-Process بدل الحكم على الشريط المرئي وحده
قاعدة بيانات "تالفة" بعد تفعيل SQLCipher قاعدة Phase 1/2 كانت SQLite عادي غير مشفَّر؛ SQLCipher لا يستطيع فتح ملف غير مشفَّر بأي مفتاح حذف بيانات الاختبار القديمة (لم تكن بيانات إنتاج فعلية) والبدء بخزنة مشفَّرة من الأساس
تصادم في رقم القيد بين جهازين وهميين أثناء الاختبار المعرّف القصير للجهاز أُخذ كـ substring خام من أول أحرف UUID — محفوف بخطر تصادم بنيوي إحصائيًا مع نمو عدد الأجهزة استبداله بهاش SHA-256 لمعرّف الجهاز (6 خانات)؛ اختبار إحصائي لاحق على 500 جهاز عشوائي أثبت عدم وجود تصادم واحد
وثائق حديثة تظهر "مؤهَّلة للإتلاف" فورًا في اختبار الاحتفاظ strftime('%s','now') في SQLite تُرجع TEXT، ومقارنتها بعدد صحيح عبر < تجعل الشرط صحيحًا دائمًا (TEXT أكبر من INTEGER دائمًا في قواعد ترتيب أنواع SQLite) بصرف النظر عن التاريخ الفعلي تمرير الوقت الحالي كمعامل مربوط من Rust بدل الاعتماد على SQL؛ اختبار Regression صريح يثبت الفخ ويمنع تكراره
جلسة مزامنة كاملة تنهار عند وصول حدث تجميد قانوني/تغيير حالة أربعة أوامر جديدة (الحالة، التجميد، الأرشيف الورقي، الإتلاف) أرسلت حقلًا واحدًا متغيّرًا فقط عبر المزامنة، بينما كل حدث "upsert" يجب أن يحمل حالة الوثيقة كاملة (قاعدة من Phase 4) دالة مشتركة full_document_payload تبني الحالة كاملة من القاعدة قبل كل حدث؛ اختبار Regression يرسل حمولة جزئية عمدًا ويتأكد من رفضها
خطأ SQL (near "(": syntax error) عند أول فتح فعلي للوحة الإتلاف بناء قائمة أعمدة list_disposal_candidates اعتمد على تقسيم نصّ DOC_COLUMNS بالفواصل ولصق d. آليًا قبل كل جزء؛ العمود المحسوب (ocr_text IS NOT NULL...) يحتوي قوسًا، فأصبح d.(...) — خطأ بناء جملة لا منطق. الاختبار السابق لم يكشفه لأنه أعاد تنفيذ استعلام مبسَّط يدويًا، لا الاستعلام الحقيقي استبدال التوليد الآلي بقائمة أعمدة صريحة (نفس نمط search_documents المُثبَت)؛ اختبار جديد يُشغِّل نص SQL الحقيقي حرفيًا، لا نسخة مبسَّطة
اختبار مزامنة سجل الإحالة يعلَق (Deadlock) ولا ينتهي أبدًا الاختبار أبقى قفل Mutex على قاعدة بيانات الجهاز المستقبِل مفتوحًا عبر متغيّر محلي، بينما استدعى مزامنة ثانية يحتاج خادم ذلك الجهاز نفس القفل لمعالجة الاتصال الوارد — جمود كلاسيكي بين خيطين تغليف كل استعلام بدالة مساعدة تُفكّ القفل فورًا بعد الاستعلام، قبل أي نقطة await تالية؛ خلل في الاختبار نفسه فقط، لا في كود الإنتاج

12.4 منهجية الاختبار: لا تسليم بلا تحقق فعلي

قاعدة عمل صارمة طُبِّقت من Phase 4 فصاعدًا: لا تسليم كود دون تشغيله فعليًا أولًا. هذا يعني عمليًا:

  • تثبيت Rust وTauri ومكتبات GTK في بيئة لينكس منفصلة خصيصًا لتشغيل cargo check الحقيقي على نفس Cargo.toml المُسلَّم — وليس فقط قراءة الكود بحثًا عن أخطاء واضحة
  • كتابة حزمة اختبار مستقلة (synccheck) تستورد وحدات المشروع الحقيقية عبر #[path = "..."]، لا نسخًا مكررة منها — فأي تعديل لاحق على الكود الأصلي يُختبَر تلقائيًا بنفس الاختبارات
  • اختبار المزامنة فعليًا بين عقدتين حقيقيتين تعملان بالتوازي عبر tokio::test(flavor = "multi_thread"): خادم WebSocket حقيقي، اتصال حقيقي، تشفير/فك تشفير حقيقي — لا محاكاة (mocks) لأي طبقة من طبقات الشبكة
  • اختبار سيناريوهات خبيثة عمدًا: مفتاح مزامنة خاطئ (يجب أن يُرفض دون تسريب بيانات)، محاولة Path Traversal عبر قيمة hash مزوَّرة، فجوة في تسلسل الأحداث — أمان النظام اختُبِر بمحاولة كسره، لا فقط بتجربة المسار السعيد
  • توليد صورة نص عربي فعلية ("وزارة الداخلية") بخط Noto Naskh Arabic، وتشغيل Tesseract عليها فعليًا عبر نفس ملف ocr.rs المشحون في المشروع — تأكيدًا أن الدعم العربي ليس افتراضًا نظريًا

النتيجة: 11 اختبار تكامل حقيقي، صفر محاكاة (mocks) للمنطق الحرج، إلى جانب cargo check وtsc --noEmit وvite build نظيفة تمامًا قبل كل تسليم.

12.5 دروس للمشاركة

  • القرار الأرخص هندسيًا غالبًا لا يكون الأكثر "أناقة". استدعاء Tesseract كعملية فرعية بدل ربطه عبر FFI بدا حلًا أقل احترافية للوهلة الأولى، لكنه وفّر تبعية بناء كاملة (libtesseract + libleptonica) كانت لتضاعف عدد مشاكل البناء التي واجهناها في Phase 3 مع OpenSSL.
  • الاختبار بين عقدتين حقيقيتين يكشف أخطاء لا يكشفها اختبار الوحدة أبدًا — تعارض أسماء المستخدمين الحتمي، وترتيب إنهاء الجلسة، ظهرا فقط عند تشغيل سيناريو كامل بين طرفين فعليين.
  • توثيق العقبة والحل لحظة حدوثهما أرخص بكثير من إعادة بنائه من الذاكرة لاحقًا لغرض المشاركة أو التوثيق.
  • القواعد المعمارية التي تضعها بنفسك تُنسى بسهولة أثناء إضافة ميزات لاحقة. قاعدة "كل حدث upsert يحمل الحالة كاملة" وُضعت بوضوح في Phase 4، ومع ذلك خُرقت أربع مرات متتالية في Phase 9 دون أن يلاحظ أحد حتى كتابة الاختبار — الحل الدائم لم يكن "انتبه أكثر"، بل دالة مشتركة واحدة يستحيل تجاوزها، واختبار Regression يفشل بصراحة لو حدث ذلك مجددًا.
  • منطق حبيس إطار العمل يُختبَر بالوكالة، والاختبار بالوكالة لا يثبت شيئًا. للمرة الثانية يكون الجذر واحدًا: list_disposal_candidates ثم full_document_payload — كلاهما كان داخل ملف يعتمد على Tauri، فكتبتُ اختبارًا لنسخة معاد كتابتها منه، فنجح الاختبار والكود الفعلي لم يُشغَّل قط. العلاج المعماري: اسحب كل منطق يستحق الاختبار إلى وحدة بلا إطار عمل، وليكن الكود الإنتاجي غلافًا رفيعًا فوقها. ومعيار بسيط قبل شحن أي واجهة تستدعي أمرًا لأول مرة: هل نُفِّذ هذا الأمر فعليًا ولو مرة؟
  • فحص آلي بين الـ backend والواجهة يكشف ما لا تكشفه المراجعة البصرية. مقارنة 43 أمرًا مسجَّلًا باستخداماتها الفعلية كشفت ثلاث ثغرات كانت مخفية لأن الواجهة "تبدو مكتملة" (دور مراجع بلا وظيفة، أرشيف ورقي بلا نموذج، حذف فئات مستحيل) — وأثناء ذلك ظهر أن البطاقة لا تعرض أسماء القسم والنوع والجهة أصلًا. وانتبه: regex بسيط يعطي إيجابيات كاذبة مع الكود المنسّق على أسطر متعددة، فتحقّق قبل الاستنتاج.
  • "صقل التصميم" ليس تصميمًا. مرحلتان سُمّيتا "UI/UX" (شعار، ألوان، أيقونات بدل إيموجي) لم تمسّا البنية إطلاقًا، فبقي التطبيق صفحة واحدة بنوافذ منبثقة لكل ميزة جديدة حتى صار يدير أكثر من 10 كيانات — ولم ينتبه أحد حتى قال المستخدم صراحة: "الـ backend كله اتغيّر والواجهة لم تتغيّر إطلاقًا". إعادة الهيكلة الفعلية كشفت أيضًا ثغرتين وظيفيتين كانتا مخفيتين خلف الواجهة القديمة: أمر create_user بلا أي واجهة منذ Phase 3 (المدير لا يضيف مستخدمًا ثانيًا)، ولا صندوق وارد للإحالات. الدرس: التصميم الحقيقي يبدأ من سؤال "كيف يتنقل المستخدم بين هذه الكيانات؟"، لا من "كيف أجمّل ما هو موجود؟".
  • حمل Mutex مفتوحًا عبر نقطة await هو أخطر أنماط الجمود في كود غير متزامن. اختبار يبدو بريئًا (قراءة قيمة من القاعدة، ثم استدعاء مزامنة أخرى) يتجمّد بصمت إلى الأبد إن كان القفل الأول لم يُفكّ بعد — القاعدة الآمنة: لا تُبقِ أي MutexGuard حيًّا عبر أي .await لاحق، ولو بدا السطر الفاصل بينهما غير ذي صلة.
  • مقارنات التاريخ/الوقت في SQL تستحق شكًّا دائمًا. دالة SQL تبدو بديهية (strftime('%s','now')) قد تُرجع نوع بيانات غير متوقَّع (TEXT بدل INTEGER)، وقواعد المقارنة الضمنية بين الأنواع تصمت عن الخطأ بدل رفضه — لا طريقة لاكتشاف هذا سوى اختبار يقارن قيمًا حقيقية قديمة وحديثة معًا، لا قيمة واحدة فقط.
  • إعادة تنفيذ المنطق في اختبار مبسَّط تُثبت الصحة المنطقية، لا صحة الكود الفعلي. اختبار منطق تواريخ الاحتفاظ نجح لأنه استخدم استعلام SQL مكتوبًا يدويًا من جديد، فلم يكتشف خطأ بناء الجملة الحقيقي في الكود الفعلي (توليد قائمة الأعمدة آليًا كسر عمودًا محسوبًا يحتوي قوسًا) — خطأ وصل لأول استخدام فعلي في الواجهة بدل أن يوقفه الاختبار. الدرس: عند الشك، يُفضَّل نسخ جملة SQL الحقيقية حرفيًا في الاختبار بدل إعادة صياغتها، أو استخلاص المنطق لدالة مشتركة واحدة يستدعيها الكود والاختبار معًا.

13. إضافة لاحقة: الهيكل التنظيمي والترقيم الإداري

بعد الإصدار الأول، وردت مجموعة اقتراحات احترافية لتحسين النظام على مستوى المحتوى والتنظيم الإداري (أقسام، أنواع وثائق، ترقيم قيد، سرية، دورة حياة، احتفاظ وإتلاف، أرشيف ورقي...). نُفِّذت الحزمة الأولى منها (الأهم والمُعتمَد عليها من بقية الحزم):

  • نظام إصدارات مخطط (Migrations) حقيقي — شرط أساسي كان ناقصًا قبل أي تعديل آخر على المخطط
  • الأقسام + أنواع الوثائق + الترقيم الإداري + السرية + حالة الوثيقة

التفاصيل الكاملة (بما فيها القرار الحسّاس حول استحالة ترقيم تسلسلي "نظيف" بلا خادم مركزي، والخلل الذي كشفه اختبار تلقائي وتم إصلاحه فورًا) موثَّقة في قسم دراسة الحالة أعلاه (12.3) وفي SETUP.md (قسم Phase 8).

تحديث: الحزم التالية نُفِّذت لاحقًا أيضًا — الاحتفاظ والإتلاف والأرشيف الورقي/QR (Phase 9)، جهات المراسلة وسجل الإحالة (Phase 10)، وحزمة fra لـ OCR والعرض الشجري للأقسام (تحسينات سريعة). التفاصيل الكاملة لكل حزمة في SETUP.md. المتبقّي الآن من الاقتراح الأصلي: الوسوم الحرة وقاموس المصطلحات، الحقول الوصفية الديناميكية حسب نوع الوثيقة، ولوحة الإحصاءات.

About

Production-ready, offline-first B2B document archiving system for government/enterprise environments. Built with Tauri 2, Rust, React, TypeScript & SQLCipher with P2P LAN sync.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages