
مسارات تدقيق مقاومة للعبث لوكلاء الذكاء الاصطناعي: سجلات Runtime Records مترابطة بسلسلة تجزئة (hash-chained)، بدون تبعيات، وقابلة للتحقق من قبل أي شخص.
# halo-record
**مسارات تدقيق مقاومة للتلاعب لوكلاء الذكاء الاصطناعي** — سجلات وقت التشغيل المتسلسلة هاشيًا، تُعرض كتقرير وقت التشغيل يمكن لعملائك التحقق منه بأنفسهم.
كل إجراء يلتقطه نظام التتبع الخاص بك (استدعاءات الأدوات، استدعاءات النموذج، الوصول إلى البيانات، الموافقات) يصبح **سجل وقت تشغيل** واحد في سجل إضافي فقط ومتسلسل هاشيًا؛ و**تقرير وقت التشغيل** هو تلك السلسلة معروضة كصفحة HTML تعيد حساب سلسلة الهاش في متصفح القارئ وتعرض ما تعرضه من السجلات التي تحققت منها (للتحقق الذي لا يعتمد على كود الصفحة نفسها، شغّل `halo verify` على ملف JSONL). أي طرف يحمل نقطة تحقق من السلسلة يمكنه التحقق من أن السجلات حتى تلك النقطة لم تُعدَّل منذ أخذها، دون الوثوق بمن أنتجها — نقطة التحقق تلك هي الجزء الحامل للثقل: السلسلة وحدها مقاومة للتلاعب ضد الجميع باستثناء الطرف المشغّل للمسجّل ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)). عندما يسأل فريق الأمن لدى العميل "ماذا فعل وكيلك ببياناتنا؟"، تسلّمهم رابطًا بدلًا من فقرة. مراجعات الأمن تطرح بالفعل أسئلة الذكاء الاصطناعي بجانب قائمة SOC 2 — وبشكل متزايد تأتي تلك الأسئلة من ISO 42001، ومواد حفظ السجلات في قانون الذكاء الاصطناعي الأوروبي، واستبيانات العملاء الخاصة. اليوم لا يزال الضمان المكتوب يمر. الرهان وراء هذا المشروع هو أنه لن يمر طويلًا.
**مُشار إليه في [Help Net Security](https://www.helpnetsecurity.com/2026/08/31/halo-record-open-source-ai-agent-audit-trail/)** (أغسطس 2026).
صيغة السجل مفتوحة ومجانية للتنفيذ. هذه الحزمة هي التنفيذ المرجعي: المسجّل، والمدقّق، وعميل الشاهد، وخادم التقرير.
> **تستخدم halo-record، أو تفكر في ذلك؟** أخبرني من أنت ولأي غرض → [من يستخدم halo-record؟](https://github.com/bkuan001/halo-record/discussions/7)
## تحقق بنفسك
يُطلب منك وضع مسجّل داخل وكيلك. لا ينبغي أن تأخذ ذلك على الثقة:
- **صفر تبعيات وقت تشغيل.** المكتبة القياسية فقط. `pip install halo-record` يثبّت حزمة واحدة بالضبط.
- **لا اتصالات شبكية**، باستثناء أربع اختيارية — التثبيت إلى شاهد (يرسل معرّف الموضوع، وعدد السجلات، وبصمتين لسلسلة الهاش — الرأس وجذر السلسلة)، وقراءة نقاط تحقق الشاهد مرة أخرى (يرسل معرّف الموضوع)، وطابع زمني RFC 3161 (يرسل فقط هاش حالة نقطة تحقق إلى جهة ختم زمني)، و`halo serve --verify-email` (يرسل رمزًا لمرة واحدة واسم موضوع التقرير، عبر SMTP من خلال الخادم الذي تهيّئه في `$HALO_SMTP_HOST`، إلى العنوان الذي يدخله عارض التقرير). جميعها معطّلة ما لم تستدعها؛ لا تخرج أي محتويات سجلات أخرى من بنيتك التحتية.
- **وسائط الأدوات الخام تُهشَّر، مع ملخص منقّح بجانبها.** تُخزَّن الوسائط كـ hash قانوني بالإضافة إلى ملخص: نص الوسيطة مع إخفاء أنماط الأسرار والبيانات الشخصية المعروفة، بحد أقصى 200 حرف. المدخل القصير الذي لا يطابق أي نمط يظهر في الملخص كاملًا؛ وضع الهاش فقط (`summaries=False`) لا يحتفظ بأي ملخص على الإطلاق. التنقيح بأفضل جهد (تعبير نمطي على صيغ الأسرار والبيانات الشخصية الشائعة بالإضافة إلى مُلتقط إنتروبيا شامل): تعامل معه كدفاع في العمق، لا كضمان. حقول النتائج التي توفرها بما يتجاوز `summary` تُفحص وتُقنَّع بنفس الأنماط، لكن لا تُقطع إلى 200 حرف (LIMITS §13).
- **صغير بما يكفي للتدقيق.** ~8,700 سطر من Python، بما في ذلك JavaScript وCSS المضمّنان في صفحة التقرير (أسطر الكود، دون احتساب الفراغات والتعليقات وسلاسل التوثيق). اقرأه كله في يوم واحد.
- **Apache-2.0.**
- **الأوراق من الدرجة الأولى.** [LIMITS.md](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md) (ما لا يمكن للسلسلة إثباته)، [PRIVACY.md](https://github.com/bkuan001/halo-record/blob/main/PRIVACY.md) (ما تحتويه السجلات وما يغادر جهازك)، [RETENTION.md](https://github.com/bkuan001/halo-record/blob/main/RETENTION.md) (العمل تحت سياسة احتفاظ)، و[REVIEWERS.md](https://github.com/bkuan001/halo-record/blob/main/REVIEWERS.md) — الفحص المستقل بأربعة أوامر بالإضافة إلى صيغة اقتباس لنتائج المراجعة.
ما يثبته كل طبقة — التمييز الحامل للثقل في هذا المشروع ([LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md)): سلسلة تحملها بنفسك تثبت أن السجلات لم تُحرَّر، نسبةً إلى رأس يحمله شخص بالفعل؛ فقط نقاط التحقق المحفوظة خارج المشغّل تثبت أن أيا منها لم يُحذف؛ ولا هاش يثبت أن كل إجراء قد التُقط.
| الادعاء | سلسلة محفوظة ذاتيًا | + نقاط تحقق خارجية | + التقاط موثوق |
|---|---|---|---|
| كشف التحريرات على أثر مُثبَّت | ✔ | ✔ | ✔ |
| كشف إعادة كتابة التاريخ المُلتزم به | — | ✔ | ✔ |
| كشف نقاط التحقق المفقودة/المتأخرة | — | ✔ (وتيرة متفق عليها) | ✔ |
| إثبات تسجيل كل إجراء | — | — | يعتمد على حدود الالتقاط |
**شاهد واحدًا قبل أن تثبّت:** [تقرير وقت تشغيل نموذجي](https://bkuan001.github.io/halo-record/sample-report.html) — بيانات خيالية، سلسلة حقيقية، ونصّه البرمجي يعيد حساب سلسلة الهاش للسجلات المضمّنة في متصفحك عند فتحه.
## عرض توضيحي في 60 ثانية
لا حاجة لوكيل. مع [uv](https://docs.astral.sh/uv/)، لا شيء لتثبيته:
```
uvx --from halo-record halo demo --serve
```
أو الطريقة الكلاسيكية:
```
pip install halo-record
halo demo --serve
```
إما أن يقوم ببناء بائع وكيل دعم خيالي مع عميلين، ويشهد السلاسل (مع ملف شاهد محلي يقوم مقام شاهد خارج المشغّل — انظر [LIMITS.md §1](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md))، ويقدّم تقارير Runtime المُقيّدة الخاصة بهم، ويفتح وحدة تحكم المشغّل في متصفحك. ثم جرّب اختبار العبث: احذف سطرًا من أحد ملفات `.jsonl` وأعد التحميل. يكتشفه التقرير.
## سجّل وكيلك الخاص
سطر واحد عند الحدود:
```python
from halo_record import trace
agent = trace(run_my_agent, profile="my-agent", log="audit.jsonl") # wraps your entrypoint; records the run boundary to ./audit.jsonl — add record_call() or a framework adapter at each tool boundary to capture individual calls
```
يُشحن أيضًا غلاف تسهيلي `from halo import ...` — لكن اسم `halo` على PyPI يخص حزمة مؤشر طرفية غير ذات صلة، وإذا كانت تلك الحزمة مثبّتة فإنها تفوز عند الاستيراد. أما `halo_record` فهو غير ملتبس، لذا تستخدمه الأمثلة.
بدون `log=`، تذهب السجلات إلى `~/.halo/my-agent.jsonl` (سلسلة واحدة لكل وكيل). يختم الغلاف حدود التشغيل؛ والأدلة موجودة في السجلات لكل استدعاء. التقطها باستخدام محوّل إطار عمل (المصفوفة أدناه) — أو صراحةً، وهو ما يُظهر أيضًا كيفية ارتباط التفويض:
```python
from halo_record import Recorder, record_call
rec = Recorder("audit.jsonl")
with record_call(rec, "crm.lookup", {"account": "acct-9"}) as call: # one sealed record per tool call
call.result = crm.lookup("acct-9")
with record_call(rec, "payments.refund", {"amount": 120},
parent_id=rec.last_record_id()) as call: # child links to the action that spawned it
call.result = payments.refund(120)
```
ثم اعرض التقرير:
```
halo report audit.jsonl -o report.html # one chain -> HTML that re-verifies in the browser
halo serve ./records --port 8721 # all tenants, gated per customer
```
ترقية دليل خُدِم لأول مرة بواسطة 0.2.45؟ يعيد `halo serve` قراءة كل منحة سابقة عند بدء التشغيل. تُعزل المنحة على ملف لا تحمل سجلاته أي معرّف موضوع (ملف `Recorder` عادي مثل `agent.jsonl`) وتُطبع عند كل بدء تشغيل مع أمر `halo grant` الذي يعيد إصدارها — انظر [LIMITS.md §17, "Serving a directory first served by 0.2.45"](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#17-one-chain-file-per-subject).
ينتهي البدء السريع عندما تنظر إلى تقرير وقت التشغيل Runtime Report الخاص بوكيلك في المتصفح. إذا حصلت على ملف JSONL وبدون تقرير، فهناك خطأ ما: افتح issue.
### كتلة التحقق
إذا تحققت طبقة حماية أو سياسة من الإجراء، يمكن أن يرافق حكمها السجل — كتلة اختيارية تسجّل ما قررته البوابة، مختومة في سلسلة التجزئة مثل كل حقل آخر:
```python
from halo_record import build
build("tool_call", "security", tool="payments.refund",
verification={"status": "allowed", "verifier": "gate/1.2",
"policy_ref": "sha256:1f3a...",
"checked_at": "2026-08-01T12:00:00Z"})
```
ملاحظة تسمية واحدة: تُصدِّر الحزمة دالة `record` (المُزيِّن)، والتي تحجب وحدة `halo_record.record` على كائن الحزمة. استورد من مسار الوحدة مباشرةً عندما تريد محتوياتها الداخلية — `from halo_record.record import build` — بدلاً من `import halo_record.record as record`.
والتي تُختَم في السجل على النحو التالي:
```json
"verification": {"status": "allowed", "verifier": "gate/1.2", "policy_ref": "sha256:1f3a...", "checked_at": "2026-08-01T12:00:00Z"}
```
`record_call(...)` يقبل نفس الكلمة المفتاحية `verification=`. الحقل `status` مطلوب داخل الكتلة؛ بينما `verifier` و`policy_ref` و`checked_at` اختيارية. معنى كل حالة:
| الحالة | ما تُبلغ عنه البوابة | هل نُفِّذ الإجراء؟ |
|---|---|---|
| `allowed` | سمحت بالإجراء | نعم — مضى الإجراء قُدُمًا |
| `blocked` | رفضت الإجراء | يُحدَّده التكامل، لا هذا الحقل — قد يحمل السجل نتيجةً مع ذلك، والحظر بحد ذاته لا يُثبت عدم التنفيذ |
| `modified` | عدّلت الإجراء قبل تنفيذه — يصف `action.input` الإجراء **كما نُفِّذ**، بعد التعديل | نعم، بالصيغة المعدَّلة |
| `unverified` | عملت (أو استُشيرت) لكنها لم تتوصل إلى قرار — وهذا يختلف عن غياب الكتلة، الذي يعني عدم تقديم أي ادعاء تحقق على الإطلاق | نعم — مضى الإجراء دون حكم |
الكتلة يوفّرها كود التكامل الخاص بالمشغّل، وهي تسجّل ما تُبلغ عنه البوابة — بنفس وضع الثقة الخاص بـ `principal` (انظر [LIMITS](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#11-verification-status-is-the-gates-report-not-halos-finding)). الختم يُثبت أن الحالة لم تُعدَّل بعد وقوعها؛ لكنه لا يُثبت أن الفحص حدث، ولا أن الحكم كان صحيحًا، ولا أن إجراءً محظورًا لم يُنفَّذ. هذا ليس تحققًا مستقلًا.
لكي يكون `policy_ref` قابلًا للاستخدام كدليل، استخدم تجزئة محتوى (content hash) لمجموعة القواعد واحتفظ بملف مجموعة القواعد — فالوسم غير القابل للحل يجعل الحقل زخرفيًا.
## الاتصال بما تشغّله بالفعل
| يُلتقط عند الحدود | يُستوعَب من القياسات الموجودة |
|---|---|
| المسجّل الأصلي (`from halo_record import trace`) | OpenTelemetry GenAI spans |
| اعتراض MCP | LiteLLM callbacks |
| LangChain / LangGraph callback | Langfuse export |
| OpenAI Agents SDK hooks | أي سجل gateway / reverse-proxy |
| Claude Agent SDK hook | خطافات `PostToolUse` في Claude Code وCodex CLI (تُطلَق بعد تنفيذ الأداة) |
تُبصم محوّلات الأطر ومسارات الاستيعاب كل سجل بوسم `source`، بحيث يكشف التقرير كيف جُمعت كل قطعة من الأدلة. تعيش السجلات الملتقطة والمستوعَبة في السلسلة نفسها.
بالنسبة إلى LangChain / LangGraph، الأمر عبارة عن callback handler:
```python
from halo_record import Recorder
from halo_record.integrations.langchain import HaloCallbackHandler
recorder = Recorder("audit.jsonl")
result = my_chain.invoke(inputs, config={"callbacks": [HaloCallbackHandler(recorder)]}) # every tool call becomes a record
```
بالنسبة لـ MCP، استدعاء واحد يغلّف جلسة العميل — وبعد ذلك *أي* وكيل يستخدم MCP يصدر سجلات لكل استدعاء أداة، بغض النظر عن الإطار الذي يقوده:
```python
from mcp import Client # mcp 2.x
from halo_record import Recorder
from halo_record.integrations.mcp import instrument_client_session
async def run(server): # an MCPServer, a server URL, or stdio parameters
async with Client(server) as client:
instrument_client_session(client, Recorder("audit.jsonl"), server="stripe")
await client.call_tool("charge", {"customer": "cus_123", "amount": 500}) # recorded
```
يتم تسجيل كل استدعاء `call_tool()` على العميل المُغلَّف، وتُختَم الأداة التي تُرجع نتيجة خطأ بحالة `error` (يقرأ المحوّل كلاً من `isError` و `is_error` في mcp 2.x). في mcp 1.x، مرِّر `ClientSession` المُهيَّأ بدلاً من `Client`؛ فالمُغلِّف هو نفسه.
بالنسبة لسجلات البوابة أو الوسيط (Cloudflare AI Gateway، Portkey، nginx أمام النموذج)، اربط صف سجل واحد في السلسلة — موسوم صراحةً كبيانات مُستوعَبة، وليس كملتقطة عند الحدود:
```python
from halo_record.integrations.gateway import record_log
record_log(Recorder("audit.jsonl"), {"tool": "gen_ai:gpt-4o", "model": "gpt-4o", "status": 200, "subject": "acme-corp"})
```
أي شيء يصدر OpenTelemetry GenAI spans (مثل CrewAI وLlamaIndex ومعظم أطر الوكلاء المزوّدة بـ OTel instrumentation) يصل إلى السلسلة عبر محوّل OTel، وتوفّر [حزمة TypeScript](https://github.com/bkuan001/halo-record-ts) محوّلات أصلية لـ Vercel AI SDK ونظام وكلاء JS. هل ينقصك محوّل لمنظومتك؟ افتح issue. معظم المحوّلات لا تتجاوز المئة سطر.
## سجّل وكيل البرمجة الخاص بك (Claude Code أو Codex)
يُطلق Claude Code خطاف `PostToolUse` بعد كل استدعاء أداة. وجّهه إلى `halo hook` ليصبح كل إجراء — كتابة الملفات، أوامر shell، استدعاءات موصل MCP — سجلًا في سلسلة محلية. دون أي تغييرات في الكود؛ إدخال واحد في الإعدادات:
```json
{
"hooks": {
"PostToolUse": [
{"matcher": "*", "hooks": [{"type": "command", "command": "halo hook"}]}
]
}
}
```
أضف ذلك إلى `~/.claude/settings.json` وستُسجَّل السجلات في `~/.halo/audit.jsonl` (يمكن تجاوز ذلك باستخدام `$HALO_LOG`). يتم تخطي الأدوات ذات التنسيق البحت التي لا تلمس أي بيانات أو شبكة أو حالة خارجية — فالسلسلة تسجّل إجراءات حدود الثقة، لا التفكير. اضبط `HALO_HASH_ONLY=1` لتسجيل تجزئات المحتوى دون ملخصات. اضبط `HALO_AGENT_VERSION` (واختياريًا `HALO_AGENT_MODEL`) لربط كل سجل ببناء الوكيل الذي أنتجه — فعندما يسأل مدقق عن الإصدار الذي كان يعمل في نافذة زمنية معينة، يجيب التصدير حسب العمود بدلًا من الاسترجاع من الذاكرة.
يوفّر Codex CLI نفس خطافات دورة الحياة بنفس شكل الأحداث (الخطافات مفعّلة افتراضيًا). أضف هذا إلى `~/.codex/hooks.json` وستُسجَّل أوامر shell الخاصة بـ Codex، وتعديلات `apply_patch`، واستدعاءات MCP في نفس السلسلة:
```json
{
"hooks": {
"PostToolUse": [
{"matcher": ".*", "hooks": [{"type": "command", "command": "halo hook"}]}
]
}
}
```
يخبر الـ hook بينهما من الحدث نفسه (يضيف Codex كلاً من `turn_id` و `model`) ويصنّف كل سجل `claude-code` أو `codex`؛ اضبط `HALO_HOOK_AGENT` لفرض أحدهما. كلاهما من الطبقة المُستوعَبة: يُطلق hook من نوع `PostToolUse` بعد تشغيل الأداة، لذا يُبنى السجل من ما تُبلّغ عنه الـ harness.
إذا كنت بحاجة إلى أن يجيب التقرير عن سؤال "تحت أي قواعد جرى هذا التشغيل؟"، فاضبط `HALO_AUTHORITY_FILE` على لقطة JSON للسلطة الفعّالة للجلسة. حافظ عليها آمنة من ناحية الخصوصية: تجزئات ومراجع، لا مطالبات خام، ولا نص سياسة خاص، ولا أسرار، ولا مخططات أدوات كاملة — تُقنَّع صيغ الأسرار والبيانات الشخصية المعروفة وقت الختم، لكن التجزئات والمراجع تمر دون مساس ولا يُكتشف النص الحر (انظر LIMITS §6). أعد استخدام `snapshot_id` فقط ما دامت السلطة الأساسية دون تغيير؛ السجلات المتتالية ذات المعرّف نفسه والمحتوى غير المتغيّر تُضغط — أما المعرّف المُعاد استخدامه مع محتوى متغيّر فيُخزَّن كاملاً، مع إشعار.
```json
{
"snapshot_id": "auth_2026_07_08T1100Z",
"captured_at": "2026-07-08T11:00:00Z",
"scope": "session",
"workspace": {"path_hash": "sha256:...", "git_commit": "abc1234"},
"refs": [
{"kind": "project_rules", "id": "CLAUDE.md", "hash": "sha256:...", "loaded": true, "truncated": false},
{"kind": "mcp_tool_registry", "id": "filesystem", "hash": "sha256:..."}
],
"omissions": [{"kind": "private_policy", "reason": "customer_secret", "hash": "sha256:..."}],
"stale_if": ["project_rules_hash_changed", "mcp_tool_registry_hash_changed"]
}
```
```sh
HALO_AUTHORITY_FILE=./authority.json halo hook
```
يُختم اللقطة (snapshot) في نفس سلسلة التجزئة (hash chain) الخاصة بسجلات الإجراءات. القيمة الافتراضية الجيدة هي لقطة واحدة على مستوى الجلسة عند البدء، بالإضافة إلى لقطة جديدة عند تغيّر القواعد أو المهارات (Skills) أو الخطافات (hooks) أو سجلات أدوات MCP أو سياسة الضغط (compaction). للحفاظ على الجلسات الطويلة خفيفة، تُضغط السجلات المتتالية التي تحمل نفس `authority.snapshot_id` بعد أول لقطة كاملة: تحتفظ السجلات اللاحقة فقط بـ `{"snapshot_id": "...", "same_as_previous": true}`. يبقى المؤشر مرتبطًا بسلسلة التجزئة، لكن كتلة refs/omissions/stale-if الضخمة لا تُكرَّر مع كل إجراء. (الضغط يتم لكل عملية مسجِّل: الالتقاط بنمط الخطافات الذي يُنشئ عملية واحدة لكل استدعاء أداة يعيد تخزين الجسم الكامل كلما لم تكن اللقطة الكاملة السابقة هي سجل الذيل، لذا تقايض العمليات قصيرة العمر حجم السلسلة مقابل حارس إعادة الاستخدام.)
يُرفق مستخدمو SDK نفس الكتلة مباشرةً — `build(..., authority={...})` أو `record_call(..., authority={...})`؛ الالتقاط بالتجزئة فقط هو نفس الواجهة (`summaries=False` على أيٍّ منها):
```python
from halo_record import Recorder, record_call
rec = Recorder("audit.jsonl")
with record_call(rec, "crm.lookup", {"account": "acct-9"},
authority={"snapshot_id": "auth_1", "rules_hash": "sha256:..."},
summaries=False) as call: # hash-only: no summaries, no excerpts
call.result = crm.lookup("acct-9")
```
ثم، المعتاد:
```
halo verify ~/.halo/audit.jsonl
halo report ~/.halo/audit.jsonl -o report.html
```
أي وقت تشغيل لوكيل (agent runtime) يعرض خطافًا (hook) بعد تنفيذ الإجراء يمكنه تغذية نفس الأمر — يقرأ الخطاف حدثًا واحدًا بصيغة JSON من stdin ويضيف سجلًا واحدًا.
سلسلة واحدة، كاتب واحد في كل مرة. السلسلة قائمة مترابطة: كاتبان يقرآن نفس الرأس (head) وكلاهما يضيف سيفرّعانها (سجلان يدّعيان نفس السلف)، وسيسمّي التحقق السجلات المتأثرة. يقوم `Recorder` بتسلسل إضافاته الخاصة بقفل جانبي (POSIX `flock` هنا؛ دليل قفل في حزمة TypeScript)، ويضيف `halo hook` عبر `Recorder`، لذا فإن إعداد الخطاف أعلاه مغطى. أي شيء يكتب ملف السلسلة مباشرة — خطاف يدوي، عمال متوازون، شاحن سجلات — يجب أن يحمل قفلًا حصريًا مكافئًا عبر تسلسل قراءة الرأس ثم الإضافة، أو الكتابة إلى سلاسل لكل عملية. [LIMITS.md section 9](https://github.com/bkuan001/halo-record/blob/main/LIMITS.md#9-single-writer-chains) يغطي هذا بالكامل، بما في ذلك الحدود بين اللغات.
## عندما يفشل التسجيل
تُكتب السجلات بعد تنفيذ الإجراء. لا يحجب Halo إجراءً عندما يتعذر كتابة سجله؛ وكتابة السجل قبل الإجراء غير متاحة.
- **الافتراضي: نتيجة الإجراء تمر كما هي.** إذا تعذر كتابة سجل (القرص ممتلئ، الصلاحيات، انتهاء مهلة قفل)، فإن `trace()` و`record_call` و`@record` ومحولات الأطر (OpenAI Agents، LangChain، LiteLLM، OpenTelemetry) وغلاف MCP و`halo hook` تُرجع نتيجة الإجراء نفسها دون تغيير، أو تعيد رفع استثناء الإجراء نفسه إذا فشل. تحذير على stderr يسمّي السجل المفقود: `halo-record: FAILED to record '<tool>' (<error>); the action ran, but its record is NOT in the evidence log. ...` (مع عدم توفر stderr قابل للاستخدام، يذهب إلى مسجّل `halo_record`). كما يحصي معالج LangChain ومصدّر OTel الخسائر (`lost_records`). لا يمكن لأي شيء في السلسلة إظهار سجل لم يُكتب قط، لذا تظل السجلات المتبقية قابلة للتحقق. المراجع الذي يراقب وتيرة نقطة تفتيش الشاهد يرى سلسلة قد توقفت؛ أما سجل مفقود واحد بين سجلات لاحقة فلا يظهر إلا في سطر stderr.
- **الوضع الصارم يرفع أيضًا.** مرّر `strict_recording=True` (إلى `trace` أو `record_call` أو `@record` أو `instrument_client_session` أو مُنشئ محوّل)، أو اضبط `HALO_STRICT_RECORDING=1`، فيُكتب نفس سطر stderr، ثم يُرفع خطأ التسجيل. يصل إلى المستدعي من `trace` و`record_call` و`@record` وغلاف MCP وخطافات OpenAI Agents ومعالج LangChain (الذي يضبط `raise_error` في LangChain). لا يمكنه الوصول إلى المستدعي من مصدّر OTel أو مسجّل LiteLLM: تلك الأطر تلتقط وتسجّل ما يرفعه مصدّر أو callback، لذا هناك يكون سطر stderr هو الإشارة في كلا الوضعين (LIMITS.md section 18). يكون الإجراء قد نُفّذ بالفعل عند رفع الخطأ، لذا فإن مستدعيًا يعتبره "فشل الإجراء" ويعيد المحاولة سيكرر الآثار الجانبية للإجراء (استرداد يُصدر مرتين). استخدم الوضع الصارم فقط حيث يكون سجل مفقود أسوأ من ذلك الخطر.
- `record_call` و`@record` بدون مسجّل (لا شيء مُمرَّر، ولا شيء مرتبط بواسطة `trace`) يرفعان `RuntimeError` قبل تشغيل الكود المُغلَّف.
- يقبل `@record` و`trace()` دوال `async def`. الغلاف نفسه `async def`: ينتظر الدالة، ثم يسجّل ما أرجعته أو رفعته (يُسجَّل الإلغاء كخطأ). يحافظ `trace()` على ارتباط مسجّله أثناء انتظار الجسم، لذا فإن أداة `record_call` أو `@record` داخل وكيل غير متزامن تستقر على السلسلة المتتبَّعة، ويحتفظ كل وكيل متزامن بسلسلته الخاصة. تُسجَّل الدالة العادية التي تُرجع كائنًا قابلًا للانتظار عند إرجاعها. تُسجَّل دالة المولّد غير المتزامن عند استدعائها، مع كائن المولّد كنتيجة لها؛ وما تُنتجه لا يُسجَّل.
## السلامة مقابل الاكتمال (اقرأ هذا الجزء)
كن دقيقًا بشأن ما تثبته كل طبقة — لأنها ادعاءات مختلفة، والاختلافات هي المقصد:
سلسلة محتفظ بها ذاتيًا تثبت **السلامة نسبةً إلى رأس مُثبَت**: بالنظر إلى رأس سلسلة يملكه شخص بالفعل، يصبح أي تعديل أو إعادة ترتيب أو حذف في السجلات خلفه قابلًا للكشف. بحد ذاتها — قبل أن يرى أي طرف خارج المشغّل رأسًا — تثبت السلسلة الاتساق الداخلي، لا التاريخ: يمكن للمشغّل إسقاط سجل وإعادة الختم، وسيتحقق الملف الجديد. تصبح السلسلة **ملتزمة تاريخيًا** في اللحظة التي يخرج فيها رأسها عن سيطرة المشغّل.
ذلك هو الشاهد: طرف خارج المشغّل يحمل نقاط تفتيش دورية للسلسلة — معرّف الموضوع، وعدد السجلات، وبصمتان للسلسلة (الرأس وجذر السلسلة)؛ الحمولة الدقيقة، ولا شيء غيرها. تجعل نقاط التفتيش إعادة كتابة التاريخ الملتزم به قابلة للكشف، ونقطة التفتيش الفائتة هي بحد ذاتها حدث مرئي:
```
halo anchor audit.jsonl witness.jsonl # anchor a checkpoint to a local witness
halo anchor audit.jsonl witness.jsonl --check # completeness verdict against it
```
بالنسبة إلى *time* تحديدًا، يستبدل ختم زمني خارجي وفق RFC 3161 ساعة نقطة التحقق المُعلنة ذاتيًا بإثبات من جهة ختم زمني (Timestamp Authority) خارج سيطرة المشغّل — "وصلت هذه السلسلة إلى هذا الرأس في موعد لا يتجاوز T"، قابل للتحقق من قبل طرف ثالث دون أي بنية تحتية مستضافة. جهة الختم الزمني الافتراضية هي freetsa.org المجانية (مناسبة للتقييم)؛ أما في بيئة الإنتاج، فوجّه `--tsa` إلى جهة ختم زمني خارج سيطرة المشغّل (مثل DigiCert / Sectigo، أو جهة يعيّنها الطرف المعتمد):
```
halo anchor audit.jsonl witness.jsonl --timestamp # attach a TSA time proof to the checkpoint
halo anchor audit.jsonl witness.jsonl --check # reads the token's claimed time
```
`--check` يؤكد أن الرمز مرتبط بحالة السلسلة هذه ويقرأ وقته الموثق، لكنه **لا** يتحقق من توقيع TSA — وهذا متروك عمدًا لأداة قياسية حتى لا يثق المراجع بأي كود من طرفنا. للتحقق من الوقت بشكل مستقل (هذا ما تسلّمه لمراجع أمني):
```
# tsa.token_b64 lives in the witness log; decode the latest one to a standard .tsr file
python3 -c 'import json,base64; cps=[json.loads(l) for l in open("witness.jsonl") if l.strip()]; t=[c["tsa"] for c in cps if c.get("tsa")][-1]; open("token.tsr","wb").write(base64.b64decode(t["token_b64"])); print(t["digest"])'
curl -s -o tsa-ca.pem https://freetsa.org/files/cacert.pem # CA for the default TSA (a commercial TSA publishes its own)
openssl ts -verify -digest <the digest printed above> -in token.tsr -CAfile tsa-ca.pem # → "Verification: OK"
```
حد آخر، يُذكر بوضوح: لا السلسلة ولا الشاهد يثبتان أن كل إجراء في العالم الحقيقي مرّ عبر المسجّل. هذه هي **اكتمال الالتقاط** — خاصية تتعلق بمكان وجود المسجّل في المكدس (القياس الأصلي، الخطافات، الاستقبال عبر البوابة)، وليست خاصية أي تجزئة. تحمل السجلات وسم `source` لهذا السبب تحديدًا. جدول الادعاءات تحت "تحقق بنفسك" في أعلى هذه الصفحة هو ملخص هذه الطبقات الثلاث.
---
[Read more](https://github.com/bkuan001/halo-record)