AI-translated from English; not yet reviewed by a fluent editor.
# حوّل مخرج الوكيل إلى سجل إجراءات مُراجَع
> أنشئ نتيجة ذات نوع محدد باستخدام OpenAI Agents SDK من ملاحظاتك، وراجع أدلة المصدر ومعناها، ثم مرّر النتيجة المقبولة إلى سجل Markdown محلي مع إعادة قراءة البصمات.
By BIG CHANGE Editorial
Published: 2026-10-20T12:52:38.610Z
Updated: 2026-10-20T12:52:38.610Z
Canonical: https://bigchange.ai/blog/agents-sdk-reviewed-action-register-guide

AI-generated conceptual illustration by BIG CHANGE.
قد تكون بنية JSON في نتيجة الوكيل صحيحة مع سوء فهم المصدر. يعطي هذا الدليل مطور Python مسارًا كاملًا من ملف ملاحظات مأذون به إلى سجل إجراءات محلي: نتيجة منتج محددة النوع، وفحص المصدر والمعنى، وموافقة بشرية على البصمات الدقيقة، ومستهلك يكتب تقرير Markdown مجمّعًا ويعيد قراءته. استخدم ملاحظاتك التي تحوي التزامات فعل صريحة. لم تُستخدم ملاحظات حقيقية أو نتيجة نموذج في إعداد الدليل.
## التغيير الكبير
- **ما الذي تغيّر:** تقبل OpenAI Agents SDK نوع Pydantic `output_type`، فيستطيع التطبيق طلب نتيجة محددة وفحصها قبل استخدامها. يضيف المثال اقتباسات المصدر وعقدًا ذا إصدار حول النتيجة.
- **لماذا يهم:** يستطيع المطور الذي يصل الوكيل بخطوة عمل أخرى إيقاف المخرجات المشوهة أو الناقصة أو غير المدعومة قبل أن تصبح قائمة إجراءات. ويبقى على المراجع الحكم على دعم الملاحظات المقتبسة لكل مهمة ومسؤول وتاريخ، وعلى أي إغفال من النموذج.
- **ما ينبغي مراقبته:** نقطة القرار عند حدود المستهلك. تنتج الوصفة سجل فرز محليًا فقط بعد الموافقة على التشغيل والمصدر والمرشح الدقيقين. والاستيراد اللاحق إلى نظام مهام يحتاج إلى سلطة وسياسة إعادة محاولة وقراءة تحقق في الوجهة خاصة به.
## جهّز مصدرًا واحدًا مأذونًا به
تحتاج إلى Python 3.12 وحساب OpenAI API مأذون للملاحظات، ومراجع يقرأ الأصل، ونموذج تأكدت من دعمه الحالي للمخرجات المنظمة بمخطط JSON في حسابك. افحص شروط البيانات والأسعار الحالية قبل تشغيل المنتج. لا تضع بيانات اعتماد في الملاحظات أو ملف الموافقة. استدعاء النموذج الوحيد أدناه هو `produce`؛ والباقي معالجة محلية.
احفظ الملاحظات الأصلية بترميز UTF-8 في `notes.txt`. يجب أن تتضمن التزامات فعل حقيقية وصريحة من عملك. عيّن `source_id` ثابتًا، مثل معرّف سجلك، وأبقِ كل بايت في الملف دون تغيير حتى المراجعة والاستهلاك. أنشئ `candidate/` قبل الإنتاج؛ يجب أن يكون ملف المرشح جديدًا. وفّر دليل إخراج منفصلًا للتقرير المحلي لاحقًا.
سجّل بصمة بايتات المصدر قبل التشغيل:
```sh
python -c "from pathlib import Path; import hashlib; print(hashlib.sha256(Path('notes.txt').read_bytes()).hexdigest())"
```
في بيئة Python 3.12 معزولة، أنشئ `recipe-requirements.txt` بهذه الأسطر الدقيقة وثبّت الحزم:
```text
openai-agents==0.24.0
pydantic==2.14.1
```
```sh
python -m pip install -r recipe-requirements.txt
mkdir candidate
```
ثُبّتت هذه الإصدارات معًا في بيئة Python 3.12.7 محلية في 20 أكتوبر 2026. أكّد التوافق وتوفر النموذج عند استخدام الدليل. احفظ النص البرمجي الكامل أدناه باسم `action_register_recipe.py` في الدليل نفسه. له أمران: `produce` ينشئ مرشحًا وإيصال تشغيل؛ `consume` يبني التقرير بعد التحقق والموافقة فقط.
## شغّل المنتج محدد النوع
العقد `Extraction` يطلب الإصدار `action-register/v1` والحالة `ready` أو `partial` أو `blocked` ومعرّف المصدر وSHA-256 لبايتات الملف الدقيقة والإجراءات و `gap_note`. يحتاج كل إجراء إلى نص واقتباس دقيق ونطاق أسطر مصدر يبدأ من 1 ومسؤول وتاريخ يسمحان بـ null، وحالة دليل `complete` أو `needs_review`. تعني قيمة null للمسؤول أو التاريخ أن الملاحظات لم تذكرهما. يُطلب من النموذج عدم استنتاجهما. كل الحقول إلزامية والإضافية مرفوضة.
اختر نموذج المخرجات المنظمة صراحةً. قدّم مسارك المسموح ومعرّفك:
```sh
python action_register_recipe.py produce --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --model YOUR-STRUCTURED-OUTPUT-MODEL
```
اقرأ `candidate/actions.json` بجانب `candidate/actions.run.json`. يسجل الأخير UUID التشغيل وبصمات المصدر والمرشح والوكيل الأخير والنموذج المطلوب. عند الفشل يُنشأ بدلًا من ذلك سجل تشخيص `.failure.json` بلاحقة معرّف التشغيل. لا يُعد الانقطاع أو `None` أو مخرج نهائي كسلسلة نصية بسيطة أو وكيل أخير غير متوقع نجاحًا. لا يحفظ هذا الأمر حالة SDK القابلة للاستئناف `RunState`؛ ولا يستطيع إيصال الفشل استئناف التشغيل. افحص الطلب والمرشح وأي حالة لاحقة قبل تقرير مناسبة تشغيل جديد.
## راجع المعنى ووافق على المرشح الدقيق
يفحص التطبيق الأنواع الصارمة والحقول المطلوبة وهوية المصدر وحالة الجاهزية وإجراءً كاملًا واحدًا على الأقل ونطاقات الأسطر الصالحة ووجود الاقتباس فيها وعدم فراغ المهمة أو المسؤول والتواريخ القياسية `YYYY-MM-DD`. هذا يثبت البنية وموضع الاقتباس، لا أن الجملة التزام حقيقي أو أن الاسم والتاريخ يعنيان ما يقوله المرشح أو أن كل الالتزامات وُجدت.
اقرأ الملاحظات الأصلية وكل إجراء مقترح. قارن المعنى والمسؤول والتاريخ والاقتباس والإغفالات. عالج الغموض بتشغيل جديد مُراجع؛ واترك المرشحين غير المدعومين أو الجزئيين أو المحجوبين دون موافقة. بعد التأكد من تطابق الملف غير المتغير والمرشح مع `actions.run.json`، اكتب `approval.json` بهذه المفاتيح السبعة فقط. انسخ معرّف التشغيل والبصمات من الإيصال المفحوص. استخدم معرّف المراجع الحقيقي ووصفًا جوهريًا للفحوص التي أُجريت فعلًا:
```json
{
"approved": true,
"run_id": "COPY-RUN-ID",
"source_id": "OWNED-ID",
"source_sha256": "COPY-SOURCE-SHA256",
"payload_sha256": "COPY-PAYLOAD-SHA256",
"reviewer": "YOUR-REVIEWER-ID",
"review_note": "Describe the source, task, owner, date, quotation and omission checks actually performed."
}
```
`approved` يجب أن يكون قيمة JSON المنطقية `true`، لا نصًا أو رقمًا. القيم الأخرى كلها سلاسل نصية؛ ولا يجوز فراغ المراجع أو الملاحظة. تفشل الحقول الإضافية. تربط الموافقة مرشحًا ومراجعة مصدر واحدة، لا أي إعادة كتابة مستقبلية.
## استهلك التقرير وأعد قراءته
```sh
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/actions.json --approval approval.json --out private-output
```
يجمع المستهلك الإجراءات المقبولة في **Assigned with dates** و **Needs an owner** و **Needs a date**. يحوي كل إدخال المهمة وحالة المسؤول والتاريخ والاقتباس وأسطر المصدر. يكتب `action-register-<payload-hash-prefix>.md` وملف `.receipt.json` مجاورًا ببصمات التشغيل والمصدر والمرشح والتقرير. افتح الملفين وقارن الإجراءات المقتبسة بالملاحظات وتحقق من `report_sha256` مقابل بايتات Markdown. لا يعني الإيصال الصالح إلا أن الملف المحلي أُنشئ وأُعيدت قراءته؛ ولا يوافق على الكتابة إلى نظام مهام خارجي.
يطبع أمر الاستهلاك مساري التقرير والإيصال الفعليين. استخدمهما لفحص البايتات:
```sh
python -c "from pathlib import Path; import hashlib,json,sys; report=Path(sys.argv[1]); receipt=json.loads(Path(sys.argv[2]).read_text(encoding='utf-8')); assert hashlib.sha256(report.read_bytes()).hexdigest()==receipt['report_sha256']; print('report hash matches receipt')" PATH-PRINTED-FOR-REPORT PATH-PRINTED-FOR-RECEIPT
```
بعد وجود مرشح، افحص مسار الرفض بمثال بنيوي اصطناعي واضح. انسخ JSON لملف منفصل واحذف أي حقل مطلوب من أول إجراء ثم استدعِ `consume` بالملف المشوه ودليل إخراج جديد فارغ. يجب أن يفشل قبل إنشاء الملف. لا تعتبر المثال استخراجًا واقعيًا ولا توافق عليه. مثلًا يحذف الفحص المحلي التالي `evidence_quote` من نسخة:
```sh
python -c "import json; from pathlib import Path; p=json.loads(Path('candidate/actions.json').read_text(encoding='utf-8')); p['actions'][0].pop('evidence_quote'); Path('candidate/SYNTHETIC-malformed.json').write_text(json.dumps(p), encoding='utf-8')"
python action_register_recipe.py consume --source-id OWNED-ID --source notes.txt --candidate candidate/SYNTHETIC-malformed.json --approval approval.json --out synthetic-rejection-output
```
توقع فشل الأمر الثاني وبقاء دليل الإخراج غير موجود. لا تستخدم المثال إن لم يكن للمرشح الحقيقي إجراء أول؛ فهو ناقص أصلًا لهذا المستهلك. عند كتابة غير مؤكدة، افحص التقرير الحتمي والإيصال قبل الإعادة. يمكن إعادة استخدام البايتات الموجودة المتطابقة؛ وتوقف البايتات المختلفة العملية للمطابقة. تغيير المصدر أو إصدار المخطط يحتاج مراجعة جديدة. لا تعِد استدعاء النموذج لمجرد غموض رد المستهلك المحلي.
## وصفة Python الكاملة
انسخ هذا النص البرمجي حرفيًا إلى `action_register_recipe.py`:
```python
"""Private, bounded recipe. No model call occurs unless `produce` is explicitly run."""
import argparse
import hashlib
import json
import uuid
from datetime import date
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr, ValidationError
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", strict=True)
class Action(StrictModel):
action_text: StrictStr
evidence_quote: StrictStr
line_start: StrictInt
line_end: StrictInt
owner: StrictStr | None
due_date: StrictStr | None
evidence_status: Literal["complete", "needs_review"]
class Extraction(StrictModel):
schema_version: Literal["action-register/v1"]
status: Literal["ready", "partial", "blocked"]
source_id: StrictStr
source_sha256: StrictStr
actions: list[Action]
gap_note: StrictStr | None
def digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def canonical(payload: Extraction) -> bytes:
return json.dumps(payload.model_dump(mode="json"), sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
def check(payload: Extraction, source_id: str, source: bytes) -> None:
if payload.source_id != source_id or payload.source_sha256 != digest(source):
raise ValueError("source identity or revision mismatch")
lines = source.decode("utf-8").splitlines()
if payload.status == "ready" and (not payload.actions or payload.gap_note is not None):
raise ValueError("ready requires actions and no gap")
if payload.status != "ready":
raise ValueError("partial or blocked result cannot reach consumer")
for index, item in enumerate(payload.actions, 1):
if item.evidence_status != "complete":
raise ValueError(f"action {index}: evidence needs review")
if not item.action_text.strip() or not item.evidence_quote.strip():
raise ValueError(f"action {index}: empty task or evidence")
if not 1 <= item.line_start <= item.line_end <= len(lines):
raise ValueError(f"action {index}: invalid source line range")
segment = "\n".join(lines[item.line_start - 1:item.line_end])
if item.evidence_quote not in segment:
raise ValueError(f"action {index}: quotation absent from source span")
if item.owner is not None and not item.owner.strip():
raise ValueError(f"action {index}: blank owner")
if item.due_date is not None:
try:
if date.fromisoformat(item.due_date).isoformat() != item.due_date:
raise ValueError()
except ValueError as exc:
raise ValueError(f"action {index}: due date must be YYYY-MM-DD") from exc
def report(payload: Extraction) -> str:
"""Application consumer: a triage register grouped by missing ownership/date."""
groups = [
("Assigned with dates", lambda x: x.owner is not None and x.due_date is not None),
("Needs an owner", lambda x: x.owner is None),
("Needs a date", lambda x: x.owner is not None and x.due_date is None),
]
rows = ["# Action register for " + payload.source_id, "", "Review artifact; source meaning was approved separately.", ""]
for heading, predicate in groups:
rows += ["## " + heading, ""]
matches = [(n, a) for n, a in enumerate(payload.actions, 1) if predicate(a)]
for number, action in matches:
rows += [f"{number}. {action.action_text}", f" Owner: {action.owner or 'Unspecified'}; due: {action.due_date or 'Unspecified'}", f" Source lines {action.line_start}-{action.line_end}: {action.evidence_quote}", ""]
if not matches:
rows += ["None.", ""]
return "\n".join(rows)
def load_payload(path: Path) -> Extraction:
return Extraction.model_validate_json(path.read_bytes(), strict=True)
def consume(source_id: str, source_path: Path, payload_path: Path, approval_path: Path, out_dir: Path) -> tuple[Path, Path]:
source = source_path.read_bytes()
payload = load_payload(payload_path)
check(payload, source_id, source)
run = json.loads(payload_path.with_suffix(".run.json").read_text(encoding="utf-8"))
payload_hash = digest(canonical(payload))
if run.get("source_id") != source_id or run.get("source_sha256") != digest(source) or run.get("payload_sha256") != payload_hash or run.get("last_agent") != "Owned-source action extractor" or not str(run.get("run_id", "")).strip():
raise ValueError("producer receipt does not match accepted candidate")
approval = json.loads(approval_path.read_text(encoding="utf-8"))
if (type(approval) is not dict
or set(approval) != {"approved", "run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"}
or approval["approved"] is not True
or any(type(approval[key]) is not str for key in ("run_id", "source_id", "source_sha256", "payload_sha256", "reviewer", "review_note"))
or not approval["reviewer"].strip() or not approval["review_note"].strip()
or approval["run_id"] != run["run_id"]
or approval["source_id"] != source_id
or approval["source_sha256"] != digest(source)
or approval["payload_sha256"] != payload_hash):
raise ValueError("human source/meaning approval must bind exact source and payload hashes")
body = report(payload).encode("utf-8")
stem = "action-register-" + payload_hash[:16]
out_dir.mkdir(parents=True, exist_ok=True)
target = out_dir / (stem + ".md")
receipt = out_dir / (stem + ".receipt.json")
record = {"run_id": run["run_id"], "source_id": source_id, "source_sha256": digest(source), "payload_sha256": payload_hash, "report_sha256": digest(body), "reviewer": approval["reviewer"], "operation": "local action-triage register"}
encoded = (json.dumps(record, indent=2) + "\n").encode("utf-8")
for path, data in [(target, body), (receipt, encoded)]:
if path.exists():
if path.read_bytes() != data:
raise ValueError(f"uncertain prior output differs: {path}; inspect before retry")
else:
with path.open("xb") as handle:
handle.write(data)
if path.read_bytes() != data:
raise IOError(f"readback failed: {path}")
return target, receipt
def produce(source_id: str, source_path: Path, candidate_path: Path, model: str) -> None:
from agents import Agent, Runner
source = source_path.read_bytes()
source_text = source.decode("utf-8")
if not source_text.strip() or candidate_path.exists() or not candidate_path.parent.is_dir():
raise ValueError("source must contain text; candidate must be new and its parent directory must already exist")
run_id = str(uuid.uuid4())
agent = Agent(
name="Owned-source action extractor",
model=model,
instructions=("Extract only explicit action commitments from the supplied numbered source. "
"Return action-register/v1. Copy an exact quote with matching 1-based line span. "
"Use null for an unstated owner or due date; never infer them. "
"Use partial and needs_review for ambiguous evidence; blocked for unusable input. "
"Do not use tools or hand off."),
output_type=Extraction,
)
numbered = "\n".join(f"{n}: {line}" for n, line in enumerate(source_text.splitlines(), 1))
try:
result = Runner.run_sync(agent, f"Source ID: {source_id}\nSHA-256: {digest(source)}\nNumbered source:\n{numbered}")
if result.interruptions or result.last_agent is not agent or not isinstance(result.final_output, Extraction):
raise ValueError("run interrupted, unfinished, or finished by unexpected agent; this CLI does not persist resumable RunState, so do not resume from its failure receipt")
output = result.final_output
if output.source_id != source_id or output.source_sha256 != digest(source):
raise ValueError("producer returned wrong source identity")
candidate = canonical(output)
with candidate_path.open("xb") as handle:
handle.write(candidate) # Candidate only; consumer still requires review.
receipt = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "payload_sha256": digest(candidate), "last_agent": result.last_agent.name, "model": model}
with candidate_path.with_suffix(".run.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(receipt, indent=2) + "\n")
except Exception as exc:
failure = {"run_id": run_id, "source_id": source_id, "source_sha256": digest(source), "error_type": type(exc).__name__, "error": str(exc), "state": "unknown_or_blocked; inspect run and destination before retry"}
with candidate_path.with_name(candidate_path.stem + "." + run_id + ".failure.json").open("x", encoding="utf-8") as handle:
handle.write(json.dumps(failure, indent=2) + "\n")
raise
def main() -> None:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
for name in ("produce", "consume"):
p = sub.add_parser(name)
p.add_argument("--source-id", required=True)
p.add_argument("--source", type=Path, required=True)
if name == "produce":
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--model", required=True)
else:
p.add_argument("--candidate", type=Path, required=True)
p.add_argument("--approval", type=Path, required=True)
p.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
if args.command == "produce":
produce(args.source_id, args.source, args.candidate, args.model)
else:
print(*consume(args.source_id, args.source, args.candidate, args.approval, args.out), sep="\n")
if __name__ == "__main__":
main()
```
## الحدود والمصادر
فُحص العقد والمستهلك المحلي دون اتصال بخمسة اختبارات وسبع حالات اصطناعية لمنتج محاكى أو إعادة محاولة. شملت توليد المخطط الصارم والرفض قبل إنشاء الملف والتجميع والبصمات ومعالجة الفشل. لم تُرسل مخططات إلى مزود ولم يُستدع نموذج ولم تُستخرج ملاحظات حقيقية ولم تُؤخذ موافقة بشرية واقعية ولم يُنشأ تقرير من مصدر حقيقي ولم تُقَس فاتورة أو جودة. تعتمد رسوم API على الحساب؛ افحص التوافق والأسعار وإذن البيانات قبل إرسال المصدر.
للسياق ذي الصلة، فإن [دليل BIG CHANGE لاختيار مهمة ذكاء اصطناعي من عملك](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) يغطي القرار السابق بشأن العمل الذي تختبره. يبدأ هذا الدليل باستخراج مختار ومأذون به، ويمرر نتيجة واحدة محددة النوع إلى مستهلك محلي مفحوص.
### المصادر وقراءات إضافية
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) يوثق Pydantic `output_type`. لا يوفر الموافقة البشرية أو المستهلك المحلي في الوصفة.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) يوثق `final_output` و `last_agent` والانقطاعات و `to_state()`. لا يحفظ CLI هذا حالة قابلة للاستئناف.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) يغطي التثبيت وإعداد مفتاح API. أبقِ المفتاح خارج المصدر والإيصالات.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) يشرح قواعد المخطط الصارم ودعم النماذج. أكّد دعم نموذجك المختار.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) يوثق التحقق الصارم ومنع الحقول الإضافية.
- [OpenAI Agents SDK 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) و [Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) يحددان الإصدارات المثبتة محليًا بدقة. لم يُختبر قبول المزود.
## Sources
- [OpenAI Agents SDK: Agents](https://openai.github.io/openai-agents-python/agents/) — دليل Pydantic output_type الرسمي. لا ينفذ الموافقة البشرية أو المستهلك المحلي للمقال.
- [OpenAI Agents SDK: Results](https://openai.github.io/openai-agents-python/results/) — السلوك الرسمي لـ final_output و last_agent والانقطاعات و to_state. لا تحفظ الوصفة RunState ولا توفر الاستئناف.
- [OpenAI Agents SDK: Quickstart](https://openai.github.io/openai-agents-python/quickstart/) — خلفية التثبيت وإعداد مفتاح API الرسمية؛ لم يُستخدم مفتاح أو استدعاء حقيقي للمزود في الإعداد.
- [OpenAI Agents SDK: مرجع مخطط المخرجات](https://openai.github.io/openai-agents-python/ref/agent_output/) — مرجع رسمي لتوليد مخطط صارم؛ الفحص المحلي لا يثبت قبول المزود.
- [OpenAI Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) — قواعد الحقول المطلوبة و additionalProperties الصارمة وتوافق النماذج؛ على القارئ التحقق من نموذجه وقت الاستخدام.
- [Pydantic ConfigDict](https://docs.pydantic.dev/latest/api/config/) — يوثق strict و extra-forbid؛ تبقى دقة المعنى مهمة مراجعة بشرية للمصدر.
- [إصدار openai-agents 0.24.0](https://pypi.org/project/openai-agents/0.24.0/) — إصدار SDK الدقيق المثبت في فحص Python 3.12.7 محلي معزول دون اتصال؛ لا استدعاء نموذج أو فاتورة.
- [إصدار Pydantic 2.14.1](https://pypi.org/project/pydantic/2.14.1/) — إصدار مكتبة التحقق الدقيق المثبت محليًا بجانب SDK 0.24.0.
- [BIG CHANGE: ارسم خريطة لعملك واختر أول مهمة لاختبارها بالذكاء الاصطناعي](https://bigchange.ai/blog/map-work-choose-first-ai-task-guide) — دليل سياقي لاختيار المهمة قبل وصفة التكامل هذه؛ لا يثبت صحة الوصفة.
نشرة BIG CHANGE البريدية
الصورة الأوسع. على وتيرتك.
قصص حديثة عن الذكاء الاصطناعي والروبوتات، وتحولات تستحق المتابعة، وأفكار عملية للتطبيق. اختر موجزًا يوميًا أو خلاصة أسبوعية أو رؤية شهرية.
موعد الإرسال القادم (UTC): . ستصلك النسخة الأولى في موعد الإرسال التالي بعد تأكيد الاشتراك.
خصوصيتك، خيارك.
يساعد التخزين الضروري على حماية الموقع وتذكّر خياراتك. تظل Google Analytics الاختيارية متوقفة حتى تسمح بها. يمكنك قراءة كل قصة باستخدام التخزين الضروري فقط. تفاصيل الخصوصية