إشعار
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تسجيل الدخول أو تغيير الدلائل.
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تغيير الدلائل.
توفر هذه الصفحة نظرة عامة على نقاط التحقق في نظام سير عمل إطار عمل عامل Microsoft.
نظرة عامة
تسمح لك نقاط التحقق بحفظ حالة سير العمل في نقاط معينة أثناء تنفيذها، والاستئناف من تلك النقاط لاحقا. هذه الميزة مفيدة بشكل خاص للسيناريوهات التالية:
- مهام سير العمل طويلة الأمد حيث تريد تجنب فقدان التقدم في حالة الفشل.
- مهام سير العمل طويلة الأمد حيث تريد إيقاف التنفيذ مؤقتا واستئنافه في وقت لاحق.
- مهام سير العمل التي تتطلب حفظا دوريا للحالة لأغراض التدقيق أو التوافق.
- مهام سير العمل التي تحتاج إلى ترحيل عبر بيئات أو مثيلات مختلفة.
متى يتم إنشاء نقاط التحقق؟
تذكر أنه يتم تنفيذ مهام سير العمل في عمليات فائقة، كما هو موثق في نموذج تنفيذ سير العمل. يتم إنشاء نقاط التحقق في نهاية كل خطوة فائقة، بعد أن أكمل جميع المنفذين في تلك البداية الفائقة تنفيذها. تلتقط نقطة التحقق حالة سير العمل بأكملها، بما في ذلك:
- الحالة الحالية لجميع المنفذين
- كافة الرسائل المعلقة في سير العمل لفوقية التالية
- الطلبات والاستجابات المعلقة
- الحالات المشتركة
Note
بدءا من الإصدار Python 1.13.0، تنشئ مهام سير العمل أيضا نقطة تحقق إدخال قبل أول خطوة فائقة لتسجيل إدخال سير العمل، ونقطة تحقق إدخال أخرى عند تسليم الاستجابات لطلب الأحداث. تجعل نقاط التحقق هذه تشغيل سير العمل الكامل قابلا لإعادة التشغيل. يتضمن هذا الإصدار تغييرات طفيفة فاصلة للتطبيقات التي تعتمد على عدد التكرارات أو معرفات مصدر الرسائل أو ترتيب نقاط التحقق. تظل نقاط التحقق الحالية مدعومة. للحصول على تفاصيل الترحيل، راجع ترقية نقاط التحقق من سير العمل Python إلى 1.13.0.
التقاط نقاط التحقق
لتمكين نقاط التحقق، CheckpointManager يجب توفير عند تشغيل سير العمل. يمكن بعد ذلك الوصول إلى نقطة التحقق عبر SuperStepCompletedEvent، أو من خلال الخاصية Checkpoints قيد التشغيل.
using Microsoft.Agents.AI.Workflows;
// Create a checkpoint manager to manage checkpoints
CheckpointManager checkpointManager = CheckpointManager.CreateInMemory();
// Run the workflow with checkpointing enabled
StreamingRun run = await InProcessExecution
.RunStreamingAsync(workflow, input, checkpointManager)
.ConfigureAwait(false);
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is SuperStepCompletedEvent superStepCompletedEvt)
{
// Access the checkpoint
CheckpointInfo? checkpoint = superStepCompletedEvt.CompletionInfo?.Checkpoint;
}
}
// Checkpoints can also be accessed from the run directly
IReadOnlyList<CheckpointInfo> checkpoints = run.Checkpoints;
لتمكين نقاط التحقق، CheckpointStorage يجب توفير عند إنشاء سير عمل. يمكن بعد ذلك الوصول إلى نقطة التحقق عبر التخزين. يشحن إطار عمل العامل ثلاثة تطبيقات مضمنة - اختر التطبيق الذي يطابق متطلبات المتانة والنشر الخاصة بك:
| الموفر | الحزمة | المتانه | الأفضل ل |
|---|---|---|---|
InMemoryCheckpointStorage |
agent-framework |
قيد المعالجة فقط | الاختبارات والعروض التوضيحية وسير العمل قصير الأجل |
FileCheckpointStorage |
agent-framework |
قرص محلي | مهام سير عمل الجهاز الواحد، التطوير المحلي |
CosmosCheckpointStorage |
agent-framework-azure-cosmos |
Azure Cosmos DB | عمليات سير عمل الإنتاج، الموزعة، عبر العمليات |
تنفذ الثلاثة البروتوكول نفسه CheckpointStorage ، بحيث يمكنك تبديل الموفرين دون تغيير سير العمل أو التعليمات البرمجية للمنفذ.
يقبل منشئو التنسيق المضمنون اختياريا name يصبح اسم سير العمل المستخدم لتجميع نقاط التحقق واكتشافها. الإعدادات الافتراضية الثابتة الخاصة بهم هي ConcurrentوSequentialGroupChatHandoff.Magentic تعيين اسم صريح عندما تشارك مهام سير عمل متعددة من نفس نوع التنسيق تخزين نقطة التحقق. استخدم نفس الاسم عند سرد نقاط التحقق الخاصة بهم أو استئنافها.
- في الذاكرة
- الملف
- Azure Cosmos DB
InMemoryCheckpointStorage يحتفظ بنقاط التحقق في ذاكرة المعالجة. الأفضل للاختبارات والعروض التوضيحية وسير العمل قصير العمر حيث لا تحتاج إلى المتانة عبر عمليات إعادة التشغيل.
from agent_framework import (
InMemoryCheckpointStorage,
WorkflowBuilder,
)
# Create a checkpoint storage to manage checkpoints
checkpoint_storage = InMemoryCheckpointStorage()
# Build a workflow with checkpointing enabled
builder = WorkflowBuilder(start_executor=start_executor, checkpoint_storage=checkpoint_storage)
builder.add_edge(start_executor, executor_b)
builder.add_edge(executor_b, executor_c)
builder.add_edge(executor_b, end_executor)
workflow = builder.build()
# Run the workflow
async for event in workflow.run(input, stream=True):
...
# Access checkpoints from the storage
checkpoints = await checkpoint_storage.list_checkpoints(workflow_name=workflow.name)
لتمكين نقاط التحقق، قم بتكوين بيئة التنفيذ مع مدير نقطة التحقق. يمكن بعد ذلك الوصول إلى نقطة التحقق من workflow.SuperStepCompletedEvent، أو من خلال قائمة نقاط التحقق الخاصة بالتشغيل.
checkpointManager := checkpoint.NewInMemoryManager()
run, err := inproc.Default.
WithCheckpointing(checkpointManager).
RunStreaming(ctx, wf, input)
if err != nil {
return err
}
defer run.Close(ctx)
var checkpoints []workflow.CheckpointInfo
for evt, err := range run.WatchStream(ctx) {
if err != nil {
return err
}
if completed, ok := evt.(workflow.SuperStepCompletedEvent); ok && completed.CompletionInfo != nil {
if completed.CompletionInfo.CheckpointInfo != nil {
checkpoints = append(checkpoints, *completed.CompletionInfo.CheckpointInfo)
}
}
}
// Checkpoints can also be accessed from the run directly.
checkpoints = run.Checkpoints()
استئناف من نقاط التفتيش
يمكنك استئناف سير عمل من نقطة تحقق معينة مباشرة على نفس التشغيل.
// Assume we want to resume from the 6th checkpoint
CheckpointInfo savedCheckpoint = run.Checkpoints[5];
// Restore the state directly on the same run instance.
await run.RestoreCheckpointAsync(savedCheckpoint).ConfigureAwait(false);
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is WorkflowOutputEvent workflowOutputEvt)
{
Console.WriteLine($"Workflow completed with result: {workflowOutputEvt.Data}");
}
}
يمكنك استئناف سير عمل من نقطة تحقق معينة مباشرة على نفس مثيل سير العمل.
# Assume we want to resume from the 6th checkpoint
saved_checkpoint = checkpoints[5]
async for event in workflow.run(checkpoint_id=saved_checkpoint.checkpoint_id, stream=True):
...
يمكنك استعادة تشغيل دفق إلى نقطة تحقق معينة مباشرة على نفس التشغيل.
// Assume we want to resume from the 6th checkpoint.
savedCheckpoint := checkpoints[5]
if err := run.RestoreCheckpoint(ctx, savedCheckpoint); err != nil {
return err
}
for evt, err := range run.WatchStream(ctx) {
if err != nil {
return err
}
if outputEvent, ok := evt.(workflow.OutputEvent); ok {
fmt.Printf("Workflow completed with result: %v\n", outputEvent.Output)
}
}
إعادة الإماهة من نقاط التفتيش
يجب أن يحافظ سير العمل المعاد ترطيبه على هويات المخطط والمنفذ لسير العمل الذي أنشأ نقطة التحقق. تعتمد كيفية حل هوية المنفذ على نوع SDK والمنفذ.
أو يمكنك إعادة ترطيب سير عمل من نقطة تحقق إلى مثيل تشغيل جديد.
// A rehydrated workflow must preserve the topology and executor identities of the workflow that
// created the checkpoint. This executor-only workflow rebuilds identically because its executors
// use fixed ids. Agent-based workflows must recreate each local agent with the same
// ChatClientAgentOptions.Id (and, if set, the same Name), otherwise the executor ids no longer
// match the checkpoint and resume fails.
var newWorkflow = WorkflowFactory.BuildWorkflow();
const int CheckpointIndex = 5;
Console.WriteLine($"\n\nHydrating a new workflow instance from the {CheckpointIndex + 1}th checkpoint.");
CheckpointInfo savedCheckpoint = checkpoints[CheckpointIndex];
await using StreamingRun newCheckpointedRun =
await InProcessExecution.ResumeStreamingAsync(newWorkflow, savedCheckpoint, checkpointManager);
Important
يجب أن يكون لسير العمل الذي تم تمريره إلى ResumeStreamingAsync نفس البنية وهويات المنفذ مثل سير العمل الذي أنشأ نقطة التحقق. إذا كان سير العمل يحتوي على مثيلات محلية ChatClientAgent يتم إعادة إنشائها عبر الطلبات أو نطاقات إدخال التبعية أو العمليات أو عمليات النشر، فعين لكل عامل مستقر ChatClientAgentOptions.Id. إذا قام عامل أيضا بتعيين Name، فاحتفظ بذلك Name دون تغيير أيضا.
على سبيل المثال، قم بتعيين معرف يمثل الدور المنطقي للعامل:
// Give each agent a stable, unique Id so its workflow executor identity stays the same when the
// workflow is reconstructed (for example per request or dependency-injection scope), which keeps
// checkpoints resumable. If an agent also has a Name, keep that stable too, since the executor
// identity includes it. Use a fixed logical role here, not a conversation, request, or user id.
internal const string IntakeAgentName = "Assistant";
public AIAgent IntakeAgent { get; } = chatClient.AsAIAgent(new ChatClientAgentOptions
{
Id = "intake-agent",
Name = IntakeAgentName,
ChatOptions = new()
{
Instructions =
"""
You receive a user request and are responsible for routing to the correct initial expert agent.
""",
},
});
تطبيق هذا النمط على كل عامل يشارك في سير العمل. يجب أن تكون معرفات العامل فريدة داخل سير العمل ويجب إعادة استخدامها عند إعادة إنشاء نفس العامل المنطقي. لا تستخدم معرفات المحادثة أو معرفات الطلبات أو معرفات المستخدم أو معلومات التعريف الشخصية أو الأسرار كمعرفات عامل.
عند تعيين عاملName، يتم اشتقاق هوية منفذ سير العمل .NET الحالية من كل Name من وId، لذا فإن تغيير أي من القيمتين يجعل سير العمل المعاد إنشاؤه غير متوافق مع نقطة التحقق. لا يؤدي تعيين قيم مستقرة إلى إصلاح نقاط التحقق التي تم إنشاؤها باستخدام معرفات مختلفة أو تم إنشاؤها عشوائيا؛ بدء جلسة عمل جديدة ونقطة تحقق بدلا من ذلك.
للحصول على السيناريوهات ذات الصلة، راجع مهام سير العمل كعواملوتزامن التسليم.
أو يمكنك إعادة ترطيب مثيل سير عمل جديد من نقطة تحقق.
from agent_framework import WorkflowBuilder
builder = WorkflowBuilder(start_executor=start_executor)
builder.add_edge(start_executor, executor_b)
builder.add_edge(executor_b, executor_c)
builder.add_edge(executor_b, end_executor)
# This workflow instance doesn't require checkpointing enabled.
workflow = builder.build()
# Assume we want to resume from the 6th checkpoint
saved_checkpoint = checkpoints[5]
async for event in workflow.run(
checkpoint_id=saved_checkpoint.checkpoint_id,
checkpoint_storage=checkpoint_storage,
stream=True,
):
...
أو يمكنك إعادة ترطيب مثيل سير عمل جديد من نقطة تحقق.
// Assume we want to resume from the 6th checkpoint
savedCheckpoint := checkpoints[5]
newWorkflow := buildWorkflow()
newRun, err := inproc.Default.
WithCheckpointing(checkpointManager).
ResumeStreaming(ctx, newWorkflow, savedCheckpoint)
if err != nil {
return err
}
defer newRun.Close(ctx)
for evt, err := range newRun.WatchStream(ctx) {
if err != nil {
return err
}
if outputEvent, ok := evt.(workflow.OutputEvent); ok {
fmt.Printf("Workflow completed with result: %v\n", outputEvent.Output)
}
}
حفظ حالات المنفذ
للتأكد من التقاط حالة المنفذ في نقطة تحقق، يجب على المنفذ تجاوز OnCheckpointingAsync الأسلوب وحفظ حالته في سياق سير العمل.
using Microsoft.Agents.AI.Workflows;
internal sealed partial class CustomExecutor() : Executor("CustomExecutor")
{
private const string StateKey = "CustomExecutorState";
private List<string> messages = new();
[MessageHandler]
private async ValueTask HandleAsync(string message, IWorkflowContext context)
{
this.messages.Add(message);
// Executor logic...
}
protected override ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellation = default)
{
return context.QueueStateUpdateAsync(StateKey, this.messages);
}
}
أيضا، للتأكد من استعادة الحالة بشكل صحيح عند استئناف من نقطة تحقق، يجب على المنفذ تجاوز OnCheckpointRestoredAsync الأسلوب وتحميل حالته من سياق سير العمل.
protected override async ValueTask OnCheckpointRestoredAsync(IWorkflowContext context, CancellationToken cancellation = default)
{
this.messages = await context.ReadStateAsync<List<string>>(StateKey).ConfigureAwait(false);
}
يصف العام AgentExecutorCheckpointStateTypedDict الحالة التي يحفظها المضمن AgentExecutor ويستعيدها:
-
cache: الرسائل المخزنة مؤقتا قبل استدعاء العامل التالي. -
full_conversation: المدخلات السابقة ومخرجات المساعد أو الأداة من أحدث تشغيل. -
agent_session: جلسة عمل العامل المتسلسلة. -
pending_agent_requests: طلبات العامل أثناء الطيران التي تم مفتاحها بواسطة معرف الطلب. -
pending_responses_to_agent: الاستجابات في قائمة الانتظار في انتظار إرسالها إلى العامل.
تستخدم الحقول المعروفة المفقودة الإعدادات الافتراضية الخاصة بها، بما في ذلك جلسة عامل جديدة عند agent_session غيابها. يتجاهل النظام الحقول غير المعروفة. يقوم حقل معروف مشوه برفع WorkflowCheckpointException.
بالنسبة للمنفذ المخصص، حدد مخصصا TypedDict لحالة نقطة التحقق الخاصة به وتحقق من صحة الحقول المعروفة في on_checkpoint_restore. رفع WorkflowCheckpointException لبيانات نقاط التفتيش غير المهينة.
لالتقاط حالة منفذ مخصص في نقطة تحقق، قم بتجاوز on_checkpoint_save الأسلوب وإرجاع الحالة كقاموس.
class CustomExecutor(Executor):
def __init__(self, id: str) -> None:
super().__init__(id=id)
self._messages: list[str] = []
@handler
async def handle(self, message: str, ctx: WorkflowContext):
self._messages.append(message)
# Executor logic...
async def on_checkpoint_save(self) -> dict[str, Any]:
return {"messages": self._messages}
أيضا، للتأكد من استعادة الحالة بشكل صحيح عند استئناف من نقطة تحقق، يجب على المنفذ تجاوز on_checkpoint_restore الأسلوب واستعادة حالته من قاموس الحالة المتوفر.
async def on_checkpoint_restore(self, state: dict[str, Any]) -> None:
self._messages = state.get("messages", [])
للتأكد من التقاط حالة المنفذ في نقطة تحقق، قم بإرفاق خطافات نقطة التحقق بالمنفذ وتخزين الحالة من خلال سياق سير العمل.
type customExecutor struct {
messages []string
}
func (e *customExecutor) Handle(message string) {
e.messages = append(e.messages, message)
}
func (e *customExecutor) OnCheckpoint(ctx *workflow.Context) error {
return ctx.QueueStateUpdate("CustomExecutorState", "", slices.Clone(e.messages))
}
استعادة الحالة في OnCheckpointRestoredFunc:
func (e *customExecutor) OnCheckpointRestored(ctx *workflow.Context) error {
value, err := ctx.ReadState("CustomExecutorState", "")
if err != nil {
return err
}
if value == nil {
e.messages = nil
return nil
}
messages, ok := value.([]string)
if !ok {
return fmt.Errorf("unexpected custom executor state type %T", value)
}
e.messages = slices.Clone(messages)
return nil
}
executorState := &customExecutor{}
custom := workflow.NewExecutor("CustomExecutor", executorState).Extend(&workflow.Executor{
OnCheckpointFunc: executorState.OnCheckpoint,
OnCheckpointRestoredFunc: executorState.OnCheckpointRestored,
}).Bind()
اعتبارات الأمان
Important
تخزين نقطة التحقق هو حد ثقة. سواء كنت تستخدم تطبيقات التخزين المضمنة أو تطبيقات مخصصة، يجب التعامل مع الواجهة الخلفية للتخزين على أنها بنية أساسية خاصة موثوق بها. لا تقم بتحميل نقاط التحقق من مصادر غير موثوق بها أو يحتمل أن يتم العبث بها.
تأكد من تأمين موقع التخزين المستخدم لنقاط التحقق بشكل مناسب. يجب أن يكون لدى الخدمات والمستخدمين المعتمدين فقط حق الوصول للقراءة أو الكتابة إلى بيانات نقطة التحقق.
تسلسل المخلل
FileCheckpointStorage، CosmosCheckpointStorage، واستخدم FoundryCheckpointStore وحدة Python pickle لتسلسل الحالة غير الأصلية ل JSON مثل فئات البيانات وتواريخ التاريخ والعناصر المخصصة. للتخفيف من مخاطر تنفيذ التعليمات البرمجية العشوائية أثناء إلغاء التسلسل، تستخدم جميع المتاجر الثلاثة أداة إلغاء تقييد بشكل افتراضي. يسمح فقط بمجموعة مضمنة من أنواع Python الآمنة (البدائيات و datetimeuuidو Decimalو والمجموعات الشائعة وما إلى ذلك) وأنواع إطار عمل العامل المدعومة أو أنواع OpenAI SDK أثناء إلغاء التسلسل. قائمة السماح ببادئة الوحدة النمطية هي نوع فقط: يتم رفض وظائف المساعد والعولمات الأخرى غير النوعية.
يحذف إطار عمل العامل القيم العابرة raw_representation من الكائنات الأصلية لإطار العمل قبل الانتقاء واستعادة هذه الحقول ك None. يؤدي أي نوع آخر غير مدعوم إلى فشل إلغاء التسلسل باستخدام WorkflowCheckpointException.
استيراد فئة تزامن مضمنة من agent_framework.orchestrations قبل تحميل نقطة تحقق تحتوي على حالة التزامن. يقوم تحميل حزمة التنسيق بتسجيل الأنواع المملوكة لإطار العمل تلقائيا، لذلك لا تحتاج إلى إضافتها إلى allowed_checkpoint_types.
للسماح بأنواع إضافية خاصة بالتطبيق، مررها عبر المعلمة allowed_checkpoint_types باستخدام "module:qualname" التنسيق:
from agent_framework import FileCheckpointStorage
storage = FileCheckpointStorage(
"/tmp/checkpoints",
allowed_checkpoint_types=[
"my_app.models:SafeState",
"my_app.models:UserProfile",
],
)
يجب حل كل allowed_checkpoint_types إدخال إلى نوع. إضافة دالة على مستوى الوحدة النمطية أو عمومية أخرى غير نوع لا تجعل هذا العمومي غير قابل للإلغاء التسلسل.
CosmosCheckpointStorage يقبل نفس المعلمة:
from azure.identity.aio import DefaultAzureCredential
from agent_framework_azure_cosmos import CosmosCheckpointStorage
storage = CosmosCheckpointStorage(
endpoint="https://my-account.documents.azure.com:443/",
credential=DefaultAzureCredential(),
database_name="agent-db",
container_name="checkpoints",
allowed_checkpoint_types=[
"my_app.models:SafeState",
"my_app.models:UserProfile",
],
)
FoundryCheckpointStore يقبل allowed_checkpoint_typesأيضا . عند ResponsesHostServer إدارة نقاط التحقق من سير العمل، قم CheckpointStoreProvider بإنشاء باستخدام هذه المعلمة وتمرير الموفر من خلال checkpoint_store_provider.
لتسجيل نوع لكل أداة فك ترميز نقطة تحقق مقيدة في عملية Python الحالية، مرر الفئة إلى register_checkpoint_type():
from agent_framework import register_checkpoint_type
from my_app.models import SafeState
register_checkpoint_type(SafeState)
ينطبق التسجيل العمومي أيضا على مثيلات التخزين التي تم إنشاؤها قبل الاستدعاء.
في المقابل، allowed_checkpoint_types ينطبق فقط على مثيل تخزين نقطة التحقق أو CheckpointStoreProvider الذي يتلقى ذلك.
تفضل المعلمة لكل متجر أو لكل موفر عندما تقوم التعليمات البرمجية بتكوين التخزين. استخدم التسجيل العمومي فقط عندما لا تتمكن التعليمات البرمجية من تكوين مثيل التخزين أو الموفر. سجل النوع قبل تحميل نقطة تحقق تحتوي عليه.
توسع الآليتان قائمة السماح المقيدة ل unpickler؛ لا يجعل بيانات المخلل غير الموثوق بها آمنة. تسجيل فئات التطبيقات الموثوق بها فقط لأن الفئة المسموح بها يمكنها تحديد سلوك إعادة بناء المخلل المخصص. يزيد التسجيل العمومي من سطح إلغاء التسلسل لكل تحميل مقيد لنقطة التحقق في العملية، لذا احتفظ بالسجل العمومي إلى الحد الأدنى المطلوب.
إذا كان نموذج التهديد الخاص بك لا يسمح بالتسلسل المستند إلى المخلل على الإطلاق، فاستخدم InMemoryCheckpointStorage أو نفذ مخصصا CheckpointStorage باستخدام استراتيجية تسلسل بديلة.
مسؤولية موقع التخزين
FileCheckpointStorage يتطلب معلمة صريحة storage_path — لا يوجد دليل افتراضي. بينما يتحقق إطار العمل من هجمات اجتياز المسار، فإن تأمين دليل التخزين نفسه (أذونات الملفات، والتشفير في حالة الثبات، وعناصر التحكم في الوصول) هو مسؤولية المطور. يجب أن يكون للعمليات المعتمدة فقط حق الوصول للقراءة أو الكتابة إلى دليل نقطة التحقق.
CosmosCheckpointStorageيعتمد على Azure Cosmos DB للتخزين. استخدم الهوية المدارة / RBAC حيثما أمكن، وقم بتحديد نطاق قاعدة البيانات والحاوية لخدمة سير العمل، وقم بتدوير مفاتيح الحساب إذا كنت تستخدم المصادقة المستندة إلى المفتاح. كما هو الحال مع تخزين الملفات، يجب أن يكون لدى الأساسيات المعتمدة فقط حق الوصول للقراءة أو الكتابة إلى حاوية Cosmos DB التي تحتوي على مستندات نقاط التحقق.
يقوم مديرو نقاط التحقق Go بتسلسل حالة نقطة التحقق ك JSON، ولكن تخزين نقطة التحقق لا يزال موثوقا به في حالة التطبيق. إذا كنت تستخدم checkpoint.NewFileSystemJSONStore، قم بتخزين ملفات نقاط التحقق في دليل محمي وتقييد الوصول للقراءة/الكتابة إلى العمليات المعتمدة فقط. المتاجر المخصصة هي المسؤولة عن التحكم في الوصول الخاص بها، والسلامة، وضمانات المتانة.