Contract: enumeration of cross-tenant tasks must SKIP + record a structured
audit finding (never fail closed); direct claim by task_id stays 403
cross_tenant_denied.
- store: add tenant_skip_audit table + record_tenant_skip_audit(task_id,
worker_id, task_tenant, worker_tenant, reason) persisting
{source:tenant, kind:cross_tenant_skip, decision:skip+audit, task_tenant,
worker_tenant} and appending TASK_TENANT_SKIPPED event; list_tenant_skip_audit
read-back for testability.
- scheduler: TaskScheduler gains tenant_of_task/worker; next_queued_task skips +
audits cross-tenant candidates (worker tenant != task tenant) instead of
selecting them.
- schedule_service: SchedulingService plumbs tenant fns into scheduler;
schedule/queued_count excludes cross-tenant with skip+audit; claim_governed
adds fail-closed 403 cross_tenant_denied guard on direct claim (depth defense).
- tests: split test_a23_cross_tenant_denied into enumeration skip+audit,
same-tenant preference, and direct-claim fail-closed.
- docs: task-plane README cross-tenant enumeration=skip+audit vs direct=fail-closed.
Verified: tests/task_plane + tests/baseline_v1_1/test_auto_claim_r2 -> 157 passed
/ 2 pre-existing failures; full task_plane+baseline_v1_1 -> 246 passed / 29
failed (failure set byte-identical to pre-existing G3 set; no new failures).
3.8 KiB
3.8 KiB
AIOS Task Plane
该模块是与任务发起方、Worker 类型和通知后端解耦的持久化任务控制平面。当前交付仅用于隔离开发与 Shadow 验收;默认 execution_enabled=false,不会修改生产、tag、Baseline 或 Registry。
架构
TaskPlaneService:统一服务边界和 HTTP 路由。TaskPlaneStore:SQLite WAL、revision/CAS、串行事务、租约、hash-chained journal、evidence 和 durable outbox。WorkerRegistry/LeaseManager:身份绑定、capability、并发额度、心跳、过期探测后恢复。DelegationEngine:父子 DAG、全局深度、capability/risk/approval/budget 单调约束和幂等重放。PolicyEngine:默认关闭执行;身份、证据、结果摘要和可插拔签名验签均 fail-closed。WorkerAdapter:OpenClaw、Codex、Hermes、A2A 和 systemd Worker 的统一契约。默认实现只返回SHADOW_VERIFIED,不执行外部命令。DurableOutboxDispatcher:Webhook、NATS JetStream、GPT callback、Dashboard 和 WeChat 的回执式通知接口。没有真实回执时始终保留NOT_DELIVERED。
状态与恢复
正常路径为 CREATED → QUEUED/CLAIMED → RUNNING → VERIFYING/COMPLETED;失败路径使用 FAILED_RECOVERABLE、FAILED_FINAL、CANCELLED 或 MANUAL_REVIEW_REQUIRED。每次状态事件都进入同一事务中的 append-only hash chain。
租约过期不会自动重放副作用。控制平面必须先调用 Worker probe,只有明确确认原执行已停止,才可将 claim 标记为 EXPIRED 并把任务转为 FAILED_RECOVERABLE。结果必须匹配 task、claim、Worker identity、未过期 lease 和 operation digest;重复结果返回冲突。
安全模型
- task/worker/claim/message ID 使用 allowlist pattern,拒绝 URL 编码差异、路径分隔符和控制字符。
- HTTP body 最大 1 MB,非对象 JSON、异常 Content-Length 和非法参数返回 400/413。
- 内部异常响应不回显路径、payload 或凭据;默认 HTTP access log 不记录原始 request target。
- secret-like 字段在 durable task、event、evidence 和 outbox 写入前脱敏。
- 结果签名存在但没有配置验签器时拒绝;摘要、evidence 或 event 被篡改时验收失败。
- SQLite 文件创建为 0600;新建状态目录使用 0700。
租户语义(cross-tenant)
任务与 Worker 各带租户标识:任务取自 task_plan.metadata.tenant_id(默认 default),
Worker 取自 trust_zone(默认 default)。跨租户(cross-tenant)处理语义为:
- 枚举 = skip + 审计:
poll/long_poll/schedule(queued_count)在遍历候选任务时, 对与 Worker 租户不一致的任务执行 SKIP,写入结构化审计 finding{source:"tenant", kind:"cross_tenant_skip", decision:"skip+audit", task_tenant, worker_tenant}(持久化于tenant_skip_audit表并在任务事件链追加TASK_TENANT_SKIPPED),不抛异常、不 fail-closed。 仅当存在同租户候选时才会被选中。 - 直接读/认领 = fail-closed:按
task_id直接governed_claim/claim_governed跨租户任务时 保持拒绝,返回403 cross_tenant_denied,不泄露任务内容。
即:Worker 只能“看到”并认领同租户任务;枚举路径让跨租户任务不可见且留有审计,直接认领路径则明确失败。
验收
PYTHONPATH=active-runtime \
bash scripts/task-plane/run_task_plane_acceptance.sh
PYTHONPATH=active-runtime \
python3 scripts/task-plane/create_shadow_tasks.py \
--database reports/task-plane-shadow.sqlite3 \
--report reports/task-plane-shadow.json
上述脚本只使用本地 SQLite 和 Shadow adapter,不触发真实 OpenClaw、Codex、Hermes、微信或生产执行。