写权限链:授权 · 写前声明 · 一次性消费 · 读回 · 孤儿 Run
主图仅画服务端连接器写(确认卡 → ActionProposal → 连接器派发);勿把「执行授权」(VerifiedWriteAuthority)与「连接器凭证」(ConnectorCredentialProvider)混为一谈。LF / Plugin / Router 对照见下文分节,不在主图展开。 图:
write-authority.sequence.json(主路径:授权 → 写前声明 → 解析连接器凭证 → CAS 消费 → 派发 → 回执 / 读回 → 结案;缺凭证走通用失败)。关联:state-authority(孤儿 Run)、local-file-v5(LF 账本)、vertical-plugin-mount(CallJournal)、trust-boundaries。 钉点:aida-agent7f2e519936b5·aida-desktop4346d79eb364。背景 issue:ai/aida-agent #255(写前声明总览)、#257(授权闸与凭证闸分离),两者在 2026-10-09 读取时仍为opened/status::inbox。 先读本机制说明,结论以 NOTES + evidence 为准。
读法:先分「tip 已有」与「目标 / 缺口」
本笔记只记录钉点源码里已经存在的机制,并单列 #255 / #257 提出但尚未实现的目标。缺口不得画成已实现。
tip 已有 ①:服务端连接器写(确认卡 → ActionProposal)— 主图范围
- 授权闸(对话轮内):
ToolCallGate.computeDecision对写工具依次做 read-only / capability 钉住 / 候选人身份 / 风险策略reject/confirm判定;最后checkCrossTurn:聊天轮次永不授权业务效应,唯一可执行路径是 Kernel 结算后的 Outbox continuation。例外只有三类委托闸:desktop域(desktop approval gate)、Plugin 域(Main 持有授权与 CallJournal)、local_file_v4(Interaction Kernel)。 - 授权落点:用户在确认卡上结算 →
BusinessEffectCoordinator.registerAuthorization(granted,带 scopeDigest、有效期)。 - 写前声明(派发前提交):
businessEffects.start以idempotencyKey = proposalId:payloadHash的哈希创建 outcome 行executing;同键重入返回已有行,不新建。 - 执行授权(≠ 连接器凭证):
InteractionWriteAuthorityOwner.issue先校验 proposal(approved、actor、payloadHash、未过期)与 Kernel 最新 accepted authority,再发出仅运行时可持有的VerifiedWriteAuthority(DTO 冒充不了)。这是执行授权对象,不是连接器登录凭证。 - 执行器内顺序(主图焦点):
toolExecutor.execute内为 回执重放检查 → 解析连接器凭证(ConnectorCredentialProvider)→ 授权消费(CAS) → 派发。缺凭证 →ConnectorNotAuthorizedError→ continuation 判定本次尝试没有完成授权 CAS(writeAuthorityConsumedHere为假),proposal 仍pending/approved时markFailed(unconsumedOnly)并经recordUnsentBusinessEffect把 outcome 记为failed(BUSINESS_EFFECT_NOT_DISPATCHED,不记unknown、不补偿);仍无「已授权待补凭证」态。不是「CAS 后再发执行授权」,也不是「有执行授权就等于有连接器凭证」。 - 一次性消费 = CAS:执行器
toolExecutor.execute在派发前调用consumeVerifiedWriteAuthority→ 再校验一次 →ActionProposalStore.markExecuting:UPDATE … SET status='executing' WHERE status='approved',写授权消费时传requireActiveRun: true,同一句 UPDATE 还要求所属 Run 未cancelled且无cancel_intent(钉点 7f2e519936b5,605e3f497):停止先提交则消费失败、不发送,消费先提交则停止排在其后并结算已认领的写;对 persistent-target 类工具还在 advisory lock +FOR UPDATE下铸persistentTargetClaimLease(执行租约)。CAS 失败 →WRITE_AUTHORITY_CONSUMED。源码注释:「Backend idempotency is deliberately not authorization」。 - 派发:带
idempotencyKey调连接器;结果recordOutcome→succeeded | failed | unknown(BUSINESS_EFFECT_TRANSITION_RULES)。 - effect 读回:连接器有 readback 合同时,「成功响应不足以结案」——
connectorReceiptConfirmsWrite不成立就observeConnectorCommittedWrite只读回查源系统;查不到 →unknown,任务进入 checking。 - 崩溃重入:重启后看到 proposal 已是
executing,永不再次调用,除非读回证明写已落地;否则记unknown。reconcileUnknown的 recheck 可回到safe_to_retry | succeeded | failed | unknown。
确认后写入的自动重试(钉点 7f2e519936b5 收窄):只有能证明写没发出去时才自动重试,也就是全新连接上的 not_sent 连接错误(ECONNREFUSED / ENOTFOUND 等),以及没有业务报文的网关 503。502、504、超时、连接重置都可能发生在业务已经提交之后,只试一次,按结果未知处理。结果分类也跟着改:网关无报文(errorBody: none)和幂等守卫的 WRITE_OUTCOME_UNKNOWN 都保持不确定,走源回读或 unknown;IDEMPOTENCY_STORE_UNAVAILABLE 算确定没执行。证据:aida-agent · packages/agent/src/orchestrator/confirmation/lifecycle.ts L1799–1859 · isConfirmedWriteRetryableError · 7f2e519936b5;packages/agent/src/runtime/continuation/business-effect-confirmed-execution.ts L1543–1574 · WRITE_OUTCOME_UNKNOWN_CODE · 7f2e519936b5
tip 已有 ②:本机文件改动(LF fs/change)
- Agent:change authorization Interaction 结算后,一个事务内
markGranted+suspendPendingResumeForRequest(Operationrequested+request_digest先落账)→ 才轮到 Desktop 拉取。 - Desktop Main
LocalFileV4ChangeExecutor.execute:同operationId已有写记录 → 只回原回执(digest 不同 →conflict);否则requireFresh确认记录 → preflight →WriteRecordStore.begin(写前记录) →ConfirmationRecordStore.consume(本机原子 mutate 内置consumedAt,已消费 →confirmation_consumed)→ 逐 actionmarkActionStarted/markActionCompleted→ 冻结回执。 - 无法证明未落地的 action 记
unknown,整单outcome_unknown;AgentterminalizeRequested以status='requested' AND request_digest条件落账。 - 读回 / 孤儿写:Desktop 启动时
recoverUnterminatedWritesAndReport→reconcileWriteFacts物理重读(sha256 + 文件身份)把unknown判为applied或保留未知,再补交回执并markDelivered。 - Agent 侧 TTL 过期回收
listExpiredRequested只覆盖fs/list|fs/search|fs/read,写操作不会被 Agent 自动过期。
tip 已有 ③:其它派发通道(对照用)
- Plugin:
PluginCallJournaladmitted → dispatched → settled,屏障失败落 uncertain / failed,禁止重启盲重放(见vertical-plugin-mount)。 - ExecutionHostRouter:
callId:bindingDigestsingleflight;handlenot_sent → possibly_sent由compareAndSet(expectedRevision)认领;possibly_sent先reconcile不盲重试。但钉点上AgentRun只为server-read宿主建 Router,handle store 缺省为InMemoryExecutionHandleStore——进程重启即丢,不是持久写前声明。
tip 已有 ④:孤儿 Run
recoverExpiredLeaseByConversation(会话入口与持久化 helper 调用)在一个事务里:先排除受保护的 Run(durable local-read、Interaction 门持有的 pending_resume、post-model entry);lease 过期的顶层 Run 若仍有 action_proposals ∈ {pending, approved, executing} 或不可重放 checkpoint 挂着 pending tool / interaction → manual_recovery + RUN_ORPHANED(「存在未确认的操作结果,需要人工恢复」);其余 → failed + RUN_ORPHANED。Run 级恢复不重放写,写的结论仍由各自账本(business effect / LF Operation / Journal)决定。
授权闸 vs 凭证闸:tip 现状
- 授权闸:
ToolCallGate(轮内)+ 执行器边界的consumeVerifiedWriteAuthority(再校验 + CAS)。 - 凭证闸:
ConnectorCredentialProvider(connectorIdForDomain→resolveStored/resolve,缺凭证 →ConnectorNotAuthorizedError→ToolExecutionError)。它在toolExecutor.execute里先于授权消费执行,没有独立命名的阶段。 - 有授权、缺凭证:continuation 判定本次尝试未消费授权 → proposal 仍
pending/approved时markFailed(unconsumedOnly),outcome 记failed(BUSINESS_EFFECT_NOT_DISPATCHED)后抛出;没有独立的「已授权、待补凭证」状态。
租约恢复、批次代际与 TLA+ 模型(钉点 e2adc9a3c325)
- 租约丢失后的恢复:business_effect 的恢复从
index.ts移入 continuation 的settleInterruptedBusinessEffect。恢复从不写源系统:已结案的直接收口;未派发的(无 outcome /executing/failed BUSINESS_EFFECT_NOT_DISPATCHED)先尝试按未发送收口;被用户重试复活的批次归重试 owner。 - 批次代际:批次成员 fence 与父结算都绑定所拥有的代际(
batchRetryKey),陈旧恢复写不动新代际。 - TLA+ 设计模型:
specs/tla/confirmed-write(单个确认写;AtMostOnce、NoConsumeAfterCancel、UnknownOnlyIfConsumed、NoApprovedLimbo、UnsentWriteFreesConversation 等,baseline 全部 PASS,Bug*.cfg变体证明检查会失败)与specs/tla/confirmed-batch(确认批次)。是协议设计检查,不是代码测试;本轮没有运行 TLC,结果取自仓内results/summary.txt。 - 审批职责分离:
sourceSeparationOfDuty在多审批人节点上不再误拒另一位审批人——识别不了操作人时,发起人与审批人重叠只在只有单一审批人时判violated,否则undetermined,交平台 / 源系统规则兜底。 - 证据:
aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-execution.tsL625–655 ·writeAuthorityConsumedHere·7f2e519936b5;aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-execution.tsL742–761 ·recordUnsentBusinessEffect·7f2e519936b5;aida-agent·packages/agent/src/session/action-proposal-store.tsL1031–1081 ·requireActiveRun·7f2e519936b5;aida-agent·packages/agent/src/runtime/continuation/business-effect-task-continuation.tsL1536–1565 ·settleInterruptedBusinessEffect·7f2e519936b5;aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-batch.tsL198–211 ·batchRetryKey·7f2e519936b5;aida-agent·specs/tla/confirmed-write/results/summary.txtL4–16 ·NoConsumeAfterCancel·7f2e519936b5;aida-agent·packages/agent/src/orchestrator/capability-policy/verticals/admin-bypass-approval.tsL147–225 ·sourceSeparationOfDuty·7f2e519936b5
VMS 表单写入合同:确认时用确认人身份重读描述(钉点 e2adc9a3c325)
- 这是 tip 已有 ① 的确认前来源复核(
assertConfirmationSourceCurrent)中新增的一步,覆盖供应商建档、项目创建、岗位需求三个写工具(18af40288/efd2255b4/71c5bc8c2)。 - 准备时:表单描述决定必填字段;只有标准字段且后台认证可写(
extraFieldsWritable)的才会问用户并自动填,其他必填字段在提问前阻断。确认卡记下formDescriptorForm、formDescriptorDigest与formValidationVersion=2(1d07cf06a起旧描述卡须重新准备)。 - 确认时:提案的 tenant / user 必须是当前确认人;以确认人身份重读当前描述,摘要变了即拒绝执行(
BUSINESS_REJECT),要求按当前字段重新准备。只有描述工具未挂载时,旧卡或显式unavailable证据才按标准路径放行。 - 不改变 CAS 消费、写前声明与读回顺序;这一步发生在授权消费之前。
- 证据:
aida-agent·packages/agent/src/runtime/continuation/vms-form-descriptor-confirmation-guard.tsL14–21 ·VMS_FORM_DESCRIPTOR_GUARDED_TOOLS·7f2e519936b5;aida-agent·packages/agent/src/runtime/continuation/vms-form-descriptor-confirmation-guard.tsL29–139 ·assertVmsFormDescriptorEvidenceCurrent·7f2e519936b5;aida-agent·packages/agent/src/runtime/continuation/operation-confirmation-source-guard.tsL62–102 ·assertVmsFormDescriptorEvidenceCurrent·7f2e519936b5;aida-agent·packages/agent/src/orchestrator/capability-policy/verticals/vms-form-extra-fields.tsL9–10 ·VMS_FORM_VALIDATION_VERSION·7f2e519936b5;aida-agent·packages/agent/src/orchestrator/capability-policy/verticals/vms-form-extra-fields.tsL86–95 ·extraFieldsWritable·7f2e519936b5 - 描述工具挂载:钉点里只有 202 增量配置脚本与在线 Skill 种子脚本把
describe_vms_form写进 VMS Skill 工具清单,仓库里没有它们对各租户的执行记录,各租户是否挂载未核。证据:aida-agent·packages/agent/scripts/apply-202-vms-closure-config.tsL565–595 ·describe_vms_form·7f2e519936b5;aida-agent·packages/agent/scripts/lib/seed-online-skills.tsL164–167 ·describe_vms_form·7f2e519936b5 - 未核:VMS 后端各租户描述工具是否已挂载;onboarding / offboarding / settlement 写入按设计文档不适用此合同。
目标 / 缺口(#255 / #257,tip 未实现)
- #255 总合同缺失:四套账本(business effect outcome、LF Operation + Desktop WriteRecord、PluginCallJournal、ExecutionHandle)各自有声明 / 重放语义,没有一份「效应类型 → 声明落点 → 重放规则」总合同;本笔记的对照只是 tip 现状记录,不等于 #255 验收(逐路径重放语义审定、「刻意不做」边界未写)。
- #255 Router 声明不持久:
InMemoryExecutionHandleStore缺省,Router 的possibly_sent认领不跨进程。 - #257 两闸未分开命名:凭证解析与授权消费同处一个执行器函数,且凭证先行;缺凭证现在会确定地收口为
failed BUSINESS_EFFECT_NOT_DISPATCHED,但仍没有独立命名的凭证闸或待补凭证态。 - #257「未过闸不入执行队列」:tip 没有显式「执行队列」概念;Outbox continuation 结算后直接执行。三类委托闸例外(desktop / plugin / local_file_v4)需要在 #257 里逐一登记为「另有闸」还是「债」。
已核实缺口(勿画成已覆盖)
2026-10-09 tip 再核(agent
61576cf06699/ desktopde6102a5a916;行号与钉点e2adc9a3c325/8eecd12081db一致)。不再留「未核实」。
- 缺凭证后 outcome 收尾 — 钉点 e2adc9a3c325 已补上(链 #257;下面是 2026-10-12 时的缺口记录,保留作对照)
现状:本次尝试未完成授权 CAS 时,executeConfirmedBusinessEffect在 proposal 仍pending/approved时markFailed(unconsumedOnly)并recordUnsentBusinessEffect(outcome →failed BUSINESS_EFFECT_NOT_DISPATCHED),outcome 不再停在executing。仍缺的是 #257 要的独立待补凭证态。
旧记录:businessEffects.start先落executing(business-effect-task-continuation.tsL1898);toolExecutor凭证解析先于 CAS(tool-executor.tsL317–321),缺凭证 →ConnectorNotAuthorizedError→ToolExecutionError(无errorType,故不进isDeterministicWriteRejectionL3047–3072)。2026-10-12 tip:确认后执行逻辑已抽到business-effect-confirmed-execution.ts(executeConfirmedBusinessEffect),语义不变——非确定性失败且 proposal 仍approved时只markFailed后重抛(L538–548),不调用recordOutcome。outcome 可停在executing。 - Desktop 离线 / fs/change 写回收 — 已核实:走另一条链 + Agent TTL 缺口(链 #255)
- Desktop 本机:已覆盖 — 启动
recoverUnterminatedWritesAndReport(main.tsL3851)→reconcileWriteFacts(write-recovery.tsL27)物理重读后补交回执(不在服务端写权限主链)。 - Agent TTL:已核实未覆盖
fs/change—listExpiredRequested仅fs/list|fs/search|fs/read(local-file-operation-store.tsL227)。 - 长期离线等待写回执的 Operation:无 Agent TTL 自动过期;Run 层或受 Interaction 保护,或 lease 过期进
manual_recovery/RUN_ORPHANED(run-store.tsL1898+;同一函数先关闭只等过期卡的 legacy 树,见state-authority),不等于写权限链回收 Operation。
- Desktop 本机:已覆盖 — 启动
视图
main— 服务端连接器写主路径(压缩;缺凭证 / 缺口 / LF 不在主图展开)。tip-vs-gaps— tip 已有 vs #255 / #257 未实现;含缺凭证通用失败。lf-compare— 本机fs/change写前记录 · 消费对照(详路径仍见local-file-v5)。business-task-and-material-gate— 业务任务操作协调器(PgBusinessTaskOperationCoordinator)、Symbol 级新鲜材料准入与任务终态尾链核验。
业务任务操作协调器与新鲜材料准入(视图 business-task-and-material-gate)
当一条 Runtime Work 绑定了企业业务事项(businessTaskBinding: { taskId, attemptId })时,所有的模型调用、本机脚本/插件回执与任务结案都受到 PgBusinessTaskOperationCoordinator(operation-coordinator.ts)与 run-work-material-check.ts 的联合管控(见视图 business-task-and-material-gate):
- Common Control 独占写权与操作状态机(
operation-coordinator.tsL152–402):- 凡是
runtime_protocol_version === 'runtime-work-v1'的 Run,若外部直接调reserve()或markReceiptPending()会立即抛出BUSINESS_TASK_COMMON_CONTROL_REQUIRED——所有状态变迁必须在 Root 锁事务内调用reserveInTransaction→markReceiptPendingInTransaction→projectReceiptInTransaction; - 状态流转:
dispatch_pending -> executing -> receipt_pending -> succeeded | failed | cancelled | outcome_unknown; - Unknown 传染阻断(L368–384, L479–498):只要任一操作落入
outcome_unknown,同一事务内立即将business_tasks.coordination_status与business_task_attempts.status雙雙置为'unknown';此后任何新一轮模型请求在admitWorkModelRequestInTx中调assertTaskModelExecutionInTransaction时,都会因为allowsKnownOperationsInTransaction返回false而被BUSINESS_TASK_OUTCOME_UNKNOWN拦截!
- 凡是
- Symbol 级新鲜材料准入许可(
FreshWorkMaterialPermit,run-work-material-check.tsL10–84 &run-work-control.tsL82–96):- 当未知操作通过补证(Supplement)恢复时,
recordWorkMaterialCheckInTx在FOR UPDATE下校验work_supplement_digest === input.digest且check.sequence > previous.sequence(单调递增防重放); - 观察记录 ≠ 执行许可:代码注释明确指出 "Persisted available/ACK is an observation, not a reusable execution permit";只有当次事务中
recordWorkMaterialCheckInTx返回fresh: true时,控制面才用模块私有 SymbolSymbol("fresh-original-owner-material")构造内存级FreshWorkMaterialPermit传入settleRuntimeWorkSkillReceiptInTx; workMaterialContinuationConsumed进一步校验补证结果不仅已 ACK,而且对应的messages (role='tool')确实写入了originalReceipt.responseDigest与supplementaryConclusion.digest,且run_checkpoints已清空原挂起等待。
- 当未知操作通过补证(Supplement)恢复时,
- 补证对账解锁与任务完成尾链核验(
operation-coordinator.tsL500–565 &run-work-business-task-completion.tsL55–140):projectWorkReconciliationInTransaction将补证写入business_task_evidence (kind='reconciliation')并投影产物;当该任务下所有outcome_unknown操作均在reconciled集合中配平后,自动将business_tasks.coordination_status恢复为'in_progress'(若canActivate为假则置'needs_attention')、business_task_attempts.status恢复为'running';- 当模型最终调用
finishBusinessTask时,createWorkBusinessTaskCompletion在withRuntimeWorkAttempt租约围栏下:先核验allowsAttemptActivationInTransaction,再FOR UPDATE锁住run_checkpoints核对accepted_response_ref对应的 Assistant 消息 SHA-256 与finishBusinessTask参数{ evidenceRefs, completionSummary }100% 吻合,最后才允许调用PgBusinessTaskCompletionCoordinator.prepareCompletionInTransaction结案。
证据与范围
aida-agent·packages/agent/src/orchestrator/tool-call-gate.tsL179–198 ·Kernel authorization·7f2e519936b5aida-agent·packages/agent/src/orchestrator/tool-call-gate.tsL340–417 ·willAwaitConfirm·7f2e519936b5aida-agent·packages/agent/src/orchestrator/tool-call-gate.tsL602–626 ·usesExternalApprovalGate·7f2e519936b5aida-agent·packages/agent/src/orchestrator/tool-call-gate.tsL676–694 ·checkCrossTurn·7f2e519936b5aida-agent·packages/agent/src/interaction-kernel/business-effect.tsL513–553 ·start·7f2e519936b5aida-agent·packages/agent/src/interaction-kernel/business-effect.tsL586–626 ·safe_to_retry·7f2e519936b5aida-agent·packages/agent/src/interaction-kernel/write-authority.tsL82–111 ·VerifiedWriteAuthorityCapability·7f2e519936b5aida-agent·packages/agent/src/interaction-kernel/write-authority.tsL157–201 ·markExecuting·7f2e519936b5aida-agent·packages/agent/src/session/action-proposal-store.tsL1031–1081 ·persistentTargetClaimLease·7f2e519936b5aida-agent·packages/agent/src/orchestrator/tool-executor.tsL300–304 ·ConnectorNotAuthorizedError·7f2e519936b5aida-agent·packages/agent/src/orchestrator/tool-executor.tsL388–440 ·consumeVerifiedWriteAuthority·7f2e519936b5aida-agent·packages/agent/src/connectors/credential-provider.tsL1–12 ·Credential boundary·7f2e519936b5aida-agent·packages/agent/src/runtime/continuation/business-effect-task-continuation.tsL2048–2076 ·registerAuthorization·7f2e519936b5aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-execution.tsL246–247 ·recovering·7f2e519936b5aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-execution.tsL461–475 ·effect.status === "unknown"·7f2e519936b5aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-execution.tsL510–524 ·writeAuthorityOwner.issue·7f2e519936b5aida-agent·packages/agent/src/runtime/continuation/business-effect-confirmed-execution.tsL625–655 ·writeAuthorityConsumedHere·7f2e519936b5aida-agent·packages/agent/src/runtime/continuation/business-effect-task-continuation.tsL1417–1435 ·observeCommittedSourceWrite·7f2e519936b5aida-agent·packages/agent/src/connectors/effect-readback/index.tsL21–28 ·observeConnectorCommittedWrite·7f2e519936b5aida-agent·packages/agent/src/runtime/local/local-file-change-continuation.tsL180–218 ·suspendPendingResumeForRequest·7f2e519936b5aida-agent·packages/agent/src/session/local-file-operation-store.tsL217–238 ·listExpiredRequested·7f2e519936b5aida-agent·packages/agent/src/execution-host/router.tsL159–191 ·bindingDigest·7f2e519936b5aida-agent·packages/agent/src/execution-host/router.tsL461–480 ·claimDispatch·7f2e519936b5aida-agent·packages/agent/src/orchestrator/run.tsL666–682 ·InMemoryExecutionHandleStore·7f2e519936b5aida-agent·packages/agent/src/session/run-store.tsL1933–2014 ·RUN_ORPHANED·7f2e519936b5aida-desktop·apps/desktop/electron/local-files-v4/change-executor.tsL145–176 ·recovery CAS·4346d79eb364aida-desktop·apps/desktop/electron/local-files-v4/change-executor.tsL494–565 ·consumeAuthorization·4346d79eb364aida-desktop·apps/desktop/electron/local-files-v4/change-executor.tsL302–331 ·outcome_unknown·4346d79eb364aida-desktop·apps/desktop/electron/local-files-v4/confirmation-record-store.tsL201–226 ·consumedAt·4346d79eb364aida-desktop·apps/desktop/electron/local-files-v4/write-recovery.tsL27–40 ·reconcileWriteFacts·4346d79eb364aida-desktop·apps/desktop/electron/main.tsL3976–3987 ·recoverUnterminatedWritesAndReport·4346d79eb364
结论来自固定版本的静态源码阅读;实现存在、默认开关和生产启用分别判断。GitLab issue 状态读取于 2026-10-09。