存储平面:Config / 运行业务表 / Desktop 本地
三层不要混
Config 平面:同一 Postgres(CONFIG_DB_URL)上的 config_schema_identity 与 CURRENT_CONFIG_SCHEMA_ID(tip:aida-config-dev-2026-10-28-conversation-turn-queue(定义在 schema-metadata.ts),链为 177 → 178 → 179 → 181 → 183 → 184 → 185 → 186 background-open-roots-index → 187 llm-credits → 188 desktop-execution → 189 local-tenant-admin → 190 document-conversion → 191 conversation-turn-queue;180 / 182 不改 identity;迁移 165 新增 runtime_work_provider_attempts 与 provider attempt ↔ run_checkpoints checkpoint guard 约束触发器,166 新增 local_file_operations.original_content,167 Work side query,168 为配置数据迁移且因与 165–167 撞号顺延);启动 assertCurrentConfigSchema 校验 identity、messages 必须是普通表(relkind=r)、关系/列/触发器/CHECK(含 PG11 compact fingerprint);缺迁移 fail fast;租户密钥与 Provider 凭据用 CONFIG_ENC_KEY AES-GCM。Admin/配置对象写这里。
运行 / 业务表平面:仍在 Config DB 内,但职责是 Run、Interaction、LF Operation、通知、runtime_work_(model rebuild/rejection/material)、conversation_compact_ pins、offer_approval_cycle_bindings 等执行事实。外部 VMS/ATS 等源系统才是业务实体 SoR。部署:审阅 migration(117–151)→ 应用 → bump schema identity → 启动断言;共享库禁止 silent DDL / reset(assertConfigDbSafeToInitialize 拒非空库)。
Desktop 本地平面:Main 持有授权文件夹、LF 写状态(write-state-v4.json,版本 3;legacy 1/2 条目解析时丢弃不可迁移的)及对话级 Interaction 草稿。钉点 8eecd12081db(e2fdc7c7)起,授权文件夹、写状态、已选文件句柄、本地 artifact 索引与原生路径 message-overlay 改存账号作用域的加密信封 LocalSecureEnvelope(根目录 <config>.native-private;随机 profile key 由 safeStorage 包裹,正文 AES-256-GCM,AAD 绑定 OS profile / installation / endpoint / tenant / user);底层文件仍由 AtomicJsonStore 原子写。旧明文 write-state-v4.json 只作迁移来源:仅 POSIX、journal 归当前账号、无待用确认 / 另存目标且能完整往返时才迁入信封并删除明文;不能无损导入、但每条写都已完成送达且确认 / 另存目标都已消费或过期的旧 journal(desktop ca745377 起,Desktop #428)改为「移到一边、不导入」:原样改名 write-state-v4.legacy-retired.json,迁移回执记下 retiredOperationIds 继续拒绝重放(回执读不出即报错)。其余情况仍 LOCAL_FILE_WRITE_STATE_LEGACY_UNAVAILABLE,明文还在期间写状态一直不可用(LEGACY_UNSCOPED);win32 上有残留明文即不可迁移。旧明文 authorized-folders-v4.json 本钉点源码不再读取,未见迁移代码(静态推断:升级后需重新授权文件夹)。这些升级路径实际是否会发生、如何处置未核实。证据:aida-desktop · apps/desktop/electron/security/local-secure-envelope.ts L48–61 · LocalSecureEnvelope · 4346d79eb364;aida-desktop · apps/desktop/electron/local-files-v4/write-state-persistence.ts L794–798 · LOCAL_FILE_WRITE_STATE_LEGACY_UNAVAILABLE · 4346d79eb364;aida-desktop · apps/desktop/electron/local-files-v4/write-state-persistence.ts L886–950 · LOCAL_FILE_WRITE_STATE_RETIRED_FILE · 4346d79eb364;aida-desktop · apps/desktop/electron/local-files-v4/authorized-folder-authority.ts L31–71 · LocalSecureEnvelope · 4346d79eb364。绝对路径不出 Main;本地仓不授权服务端工作,也不随用户跨设备。
失败 / 耦合
- schema_id 不匹配或缺 REQUIRED 关系/列 → 进程拒绝启动,不静默降级。
- 已有对象上跑 initialize → 明确拒绝,要求走审阅迁移链。
- Desktop 本地文件损坏只影响本机投影,不污染 Config DB。
迁移 169–173(钉点 e2adc9a3c325)
- 原来同号的两个 169 已拆开:取消操作约束改名为
170_employee_lifecycle_cancellation_operations.sql,db:migrate仍接受旧名并提示改名;SQL 主体不变,可以重复执行。 169_restore_config_structure.sql现在同时接受取消前、取消后两种约束定义:健康环境只执行 170;需要修复的环境先 169 再 170,顺序反了也不再REPAIR_OBJECT_CONFLICT。171:从在线 Skillvms-employee-lifecycle移除离项个人应付来源工具;173:给payroll-work-command追加只读list_payroll_book_members。都是纯数据、可重放,README 写明未在任何环境执行。- 以上都不改 schema identity(当时为
aida-config-dev-2026-10-10-work-side-queries,之后由 177 推进)。 - 编号(钉点 e2adc9a3c325,!219 合入后):170 已被取消约束占用;角色按租户迁移改为预留 172(文件尚未提交、尚未执行),171 / 173 文件头与 README 已一致。两份草案(
170-role-capability-answer-DOWN-DRAFT.sql、tenant-resource-tiers-DRAFT.sql)仍写「170」,README 说正式提交时随之改为 172;资源档位草案写明执行前需 Max 确认。
迁移 174 / 176 / 177(钉点 e2adc9a3c325)
174:给audit_events补 2026-11 到 2028-12 的月分区,不加 DEFAULT,不改 identity。README 记载审阅版已在 150、202 执行;截止 2026-11-01 前两库都要有分区。176:放宽desktop_distribution_releases平台 CHECK,加入 linux-x64(Linux 发布链路见desktop-client),不改 identity。177:给分区父表audit_events加可空state/failure_class两列,不加 CHECK、不改旧行;identity 推进到aida-config-dev-2026-10-16-audit-state-contract。启动断言要求目标 identity 和这两列都在。- 三条都不随启动执行。README 177 段仍写「未在 150、202 或任何共享库执行」,但同一 README 记载 202 已处在 179 identity、150 已依次执行 178 / 179 / 181 / 182——都以 177 为前置,177 段这句已过时。175 预留已取消,messages 去分区改由 181 完成。
- 证据:
aida-agent·packages/agent/migrations/README.mdL163–167 ·177_audit_state_failure_class.sql·e2adc9a3c325;packages/agent/migrations/177_audit_state_failure_class.sqlL125–128 ·aida-config-dev-2026-10-16-audit-state-contract·e2adc9a3c325
迁移 180–185 与 ADR-0094 / 0095(钉点 e2adc9a3c325)
- identity 链:179 →
181_messages_unpartition(aida-config-dev-2026-10-21-messages-unpartitioned)→183_conversation_seq_quota_bucket(…-conversation-seq-quota-bucket)→184_quota_bucket_kind_assignment(…-quota-bucket-kind-assignment)→185_background_runs_outside_conversation_slot(…-background-outside-conversation-slot)→186_background_open_roots_index(…-background-open-roots-index,即 tip;部分索引idx_agent_runs_background_open_roots)。每步前置只接受上一步或本步目标值,目标重放只核对、不写;均 PG11/12 单事务、lock_timeout=5s、表属主SET LOCAL ROLE,开头按 ADR-0094 一次取齐业务表锁、之后不升级。 - messages 普通表(180 / 181 / 182,STA-84):180 给当时的分区表补 26 个月分区(只在 202 执行过,已被 181 取代);181 把 messages 收敛为普通表(202 走复制路径、保留
messages_partitioned_old;150 本已是普通表,只核对结构),启动校验要求relkind=r;182 在核对归档每行都在 messages 后删除归档表,不改 identity。 - 会话序号(183,ADR-0095 2a-1 / 2a-2):
conversations.next_message_seq+ BEFORE INSERT 触发器串行分配messages.conversation_seq(覆盖调用方值,允许空洞,不可改,存量 NULL 不回填)。读路径统一按conversation_seq ASC NULLS FIRST, created_at, id(session/message-order.ts);前台上下文快照以本轮原始输入消息的序号为截止,晚到的外部推送 / 后台汇报下一轮才可见;旧摘要在新顺序下不是完整前缀时改用无摘要窗口并告警。ADR 禁止以 INSERT 恢复历史消息。已知例外:附件来源截止仍按(created_at, id)。 - 配额分桶与后台写入(183 / 184 / 185):
agent_runs.quota_bucket由触发器推导(conversation turn → foreground;background_work.v1及其 child → background;其余 uncounted),184 修复「先 NULL kind 建行、受理时再赋 kind」两阶段路径的首次推导,之后不可改桶、不可把非 NULL kind 清回 NULL;后台 root 只能写assistant / background_report消息,后台 child 不得写消息;origin_run_id可空、不可变、无外键。185 让一会话一活跃 run 的唯一索引排除background_work.v1。领取侧见scaling-roles。 - 锁层级(ADR-0094,Proposed,代码已落地):L1 原 root → L2 carrier/child → L3 run 附属行 → L4 消息 id advisory → L5 conversations → L6 sessions → L7 已有 messages;持有 conversations 行锁后不得再阻塞等待已有 run 行锁。受理据此删掉旧
runtimeExecution分支与对已有 root 的FOR UPDATE,挂起本机执行改为逐表显式加锁,过期卡收敛改为非阻塞锁整棵树(见state-authority)。ADR 自述不是全库无死锁证明。 - 执行记录(README,2026-10-21):150(PG 12.22)依次执行 178、179、181、182;202(PG 11.16)在 179 identity 上执行 181(首次锁超时整体回滚,修正后重跑)、182,并已执行 183。184 / 185 两库都只有执行说明、没有执行记录,150 尚未执行 183。按 README,183 / 184 / 185 每步执行后都要立刻发布匹配镜像,旧镜像过不了新 identity。README 181 段末尾仍留有「150:未执行」旧句,与 182 段矛盾。
- 证据:
aida-agent·packages/agent/src/config/schema-metadata.tsL6–8 ·aida-config-dev-2026-10-28-conversation-turn-queue·e2adc9a3c325;aida-agent·packages/agent/src/config/schema-identity.tsL691–699 ·relkind·e2adc9a3c325;aida-agent·packages/agent/src/config/ddl.tsL4812–4828 ·assign_conversation_message_seq·e2adc9a3c325;aida-agent·packages/agent/src/config/ddl.tsL1348–1355 ·idx_agent_runs_one_active_per_conversation·e2adc9a3c325;aida-agent·packages/agent/src/session/message-order.tsL7–12 ·messageOrderSql·e2adc9a3c325;aida-agent·docs/adr/0094-config-db-transaction-lock-hierarchy.mdL27–41 ·持有 conversations 行锁·e2adc9a3c325;aida-agent·docs/adr/0095-conversation-seq-quota-bucket-background-write.mdL13–24 ·next_message_seq·e2adc9a3c325;aida-agent·packages/agent/migrations/README.mdL198–203 ·182_drop_messages_partitioned_old.sql·e2adc9a3c325
迁移 187–191(钉点 e2adc9a3c325)
- 187
llm_credits→ identityaida-config-dev-2026-10-26-llm-credits:租户/用户 Credits 配额与用量表;chat 受理admitChatCredits,耗尽返回专用拒绝。共享部署需单独授权。 - 188
desktop_execution_devices→…-desktop-execution:设备登记 / online lease /runtime_invocations_remote_desktop;为远程 Desktop 执行就绪(设计稿docs/design/remote-desktop-readiness.md)。代码已合入;共享库执行与真机远程验收未核实。 - 189
local_tenant_admin→…-local-tenant-admin:本地操作员角色扩到tenant_admin且强制具体租户作用域。 - 190
document_conversion→…-document-conversion:guard_local_file_original_content增加convert_pdf_to_xlsx/document_conversion消费方。 - 191
conversation_turn_queue→ tip…-conversation-turn-queue:会话仍在执行时,新 chat turn 服务端 FIFO 排队(加密载荷),不改一会话一活跃 run 唯一索引;ChatSubmissionStore经insertConversationTurnQueue/claimNext…/mark…Admitted驱动。 - README 没有 150/202 对 187–191 的执行记录;未升到 tip identity 的库上本钉点 Agent 会拒绝启动。旧债 184/185/186 共享库执行状态仍未核实。
- 证据:见本轮 evidence
storage-planes新增 claim。
迁移 179(钉点 e2adc9a3c325)
179_local_file_transient_expiry.sql(Config DB):前置 identity 只接受aida-config-dev-2026-10-18-desktop-protocol-v3或目标值(可重放),并按 md5 校验现有守卫函数体与触发器形状,否则TRANSIENT_EXPIRY_*_PRECONDITION_FAILED/TRANSIENT_EXPIRY_GUARD_DRIFT;事务内只CREATE OR REPLACE guard_local_file_original_content,新增写入者runtime_work.original_file_writer = 'transient-expiry':只允许 UPDATE 把已过期的transient_result/transient_result_expires_at置空,行其余字段(含原件)不变;不改表结构;identity 推进到aida-config-dev-2026-10-20-local-file-transient-expiry。带 DOWN 草稿docs/migration-drafts/rollback/179-local-file-transient-expiry-DOWN.sql。- 运行时写入者:
createLocalFileMaintenance.runOnce在事务里设置该写入者后调expireUnconsumedTransientResults。 - SQL 头注释写明共享环境部署需单独授权。179 现为 181 的唯一前置;执行记录见下文「迁移 180–185」。
- 证据:
aida-agent·packages/agent/migrations/179_local_file_transient_expiry.sqlL1–30 ·TRANSIENT_EXPIRY_SCHEMA_PRECONDITION_FAILED·e2adc9a3c325;aida-agent·packages/agent/migrations/179_local_file_transient_expiry.sqlL324–337 ·transient-expiry·e2adc9a3c325;aida-agent·packages/agent/migrations/181_messages_unpartition.sqlL23–24 ·aida-config-dev-2026-10-20-local-file-transient-expiry·e2adc9a3c325;aida-agent·packages/agent/src/runtime/local/local-file-maintenance.tsL29–34 ·transient-expiry·e2adc9a3c325
Work 展示账本(钉点 e2adc9a3c325)
- 生产 Chat Work 在 common-control 事务里把公开事实投影写入
run_projection_events(producerchat-sse-work,dense 序号复用producer_revision,推进agent_runs.projection_next_sequence);每 Root 上限 10000 条 / 16 MiB,超限写chat_sse_truncated。投影在 savepoint 内执行:失败只回滚展示写入,在 savepoint 外写截断标记,Work 核心状态照常提交。供只读GET /api/runs/:runId/stream重放(见observability-map)。未改 DDL / 迁移 / identity;ER-09 静态写入者清单已把它登记为受审 Run 写入者(只分配展示序号,不碰 status / attempt / checkpoint)。 - 证据:
aida-agent·packages/agent/src/runtime/stream/work-chat-sse-ledger.tsL24–50 ·WORK_CHAT_SSE_MAX_EVENTS·e2adc9a3c325;aida-agent·packages/agent/src/runtime/stream/work-chat-sse-ledger.tsL192–206 ·savepoint·e2adc9a3c325
迁移 178(钉点 e2adc9a3c325;现为 179 的前置)
178_desktop_protocol_v3.sql(Config DB):前置 identity 必须是aida-config-dev-2026-10-16-audit-state-contract(已是目标时可重放),否则DESKTOP_PROTOCOL_V3_PREDECESSOR_MISMATCH;事务内只CREATE OR REPLACE两个守卫函数——guard_local_file_original_content去掉对request->'features'token 的判断,local_file_operations_assert_work_identity改看localFileCapability是否存在而不是localFileProtocolVersions——不改行与约束形状;identity 推进到aida-config-dev-2026-10-18-desktop-protocol-v3(本钉点已被 179 继续推进)。- 启动必需清单同时不再要求
chat_submission_idempotency.original_inline_inputs。 - 运行时只做 identity 检查,从不执行此 SQL。ADR-0090 D5 要求部署到 202 等远程环境前先应用 178。README 没有单独的 178 段落;执行情况记在 181 / 182 段(150 于 2026-10-21 先执行 178、179;202 执行 181 时已处在 179 identity)。
- 遗留:
business_task_agent_turn_outbox仍在必需清单里,但源码已无写入方(见harness)。 - 证据:
aida-agent·packages/agent/migrations/178_desktop_protocol_v3.sqlL1–16 ·aida-config-dev-2026-10-18-desktop-protocol-v3·e2adc9a3c325;aida-agent·packages/agent/migrations/178_desktop_protocol_v3.sqlL39–43 ·DESKTOP_PROTOCOL_V3_PREDECESSOR_MISMATCH·e2adc9a3c325;aida-desktop·apps/desktop/electron/local-files-v4/write-state-persistence.tsL255–270 ·protocolFeatures·4346d79eb364
归属边与非目标
- Owner:Config DB schema 与 Agent 运行表;Desktop 本地状态(LocalSecureEnvelope 加密信封 + AtomicJsonStore 原子写)仅本机。
- 非目标:不讨论对象存储字节面(S3 协议元数据另册);不写 RustFS。
- 相关:
state-authority、ledger-authority、local-file-v5、overview。
视图
-
main— 三平面架构。 -
migrate— schema identity 迁移/部署耦合序(图里的迁移号是示意;现行 identity 链见上文「迁移 180–185」)。 -
fail-schema— 启动 schema 断言 fail-fast。 -
fail-atomic— AtomicJsonStore corrupt / LEGACY_DROPPED。 -
ok-schema— schema identity 断言通过(对偶fail-schema)。 -
ok-atomic— AtomicJson 读写干净(对偶fail-atomic)。
成功对偶(与失败链入口相同)
- ok-schema:
assertCurrentConfigSchema匹配 schema_id 且关系/列完备 → 启动继续。 - ok-atomic:
AtomicJsonStore.readstatus=ok;原子写;legacy drop=0。
失败链(深挖)
- fail-schema:
assertCurrentConfigSchemamismatch/缺列 → throw;进程拒启;恢复=审阅迁移+bump+重启。 - fail-atomic:corrupt 不覆写空态;unmigratable legacy →
LOCAL_FILE_WRITE_STATE_LEGACY_DROPPED;仅本机。
证据与范围
-
aida-agent·packages/agent/src/config/schema-identity.tsL4–42 ·CURRENT_CONFIG_SCHEMA_ID·e2adc9a3c325 -
aida-agent·packages/agent/src/config/schema-identity.tsL633–663 ·config_schema_identity·e2adc9a3c325 -
aida-agent·packages/agent/src/config/schema-identity.tsL661–701 ·assertCurrentConfigSchema·e2adc9a3c325 -
aida-agent·packages/agent/src/config/db-init.tsL176–206 ·assertConfigDbSafeToInitialize·e2adc9a3c325 -
aida-agent·packages/agent/src/config/crypto.tsL15–45 ·initEncryption·e2adc9a3c325 -
aida-agent·docs/architecture/backend-admin.mdL76–98 ·Config DB·e2adc9a3c325(历史引用:该文件在上一钉点与当前钉点均不存在,未能复核;见 ATLAS-DEBT) -
aida-desktop·apps/desktop/electron/local-files-v4/authorized-folder-authority.tsL119–159 ·V4AuthorizedFolderStore·4346d79eb364 -
aida-desktop·apps/desktop/electron/local-files-v4/write-state-persistence.tsL36–67 ·LOCAL_FILE_WRITE_STATE_FILE·4346d79eb364 -
aida-desktop·apps/desktop/electron/atomic-json-store.tsL68–97 ·AtomicJsonStore·4346d79eb364 -
aida-desktop·apps/desktop/electron/local-files-v4/write-state-persistence.tsL730–746 ·LOCAL_FILE_WRITE_STATE_LEGACY_DROPPED·4346d79eb364 -
aida-agent·packages/agent/src/config/schema-identity.tsL661–701 ·assertCurrentConfigSchema·e2adc9a3c325 -
aida-desktop·apps/desktop/electron/local-files-v4/write-state-persistence.tsL459–469 ·readstatus=ok ·4346d79eb364
结论来自固定版本的静态源码与合同阅读;实现存在、默认开关和生产启用分别判断。本站代码证据面板列出声明状态与具体限制。
-
aida-agent·packages/agent/migrations/README.mdL133–141 ·170_employee_lifecycle_cancellation_operations.sql·e2adc9a3c325 -
aida-agent·packages/agent/migrations/171_remove_offboarding_payable_skill_tool.sqlL1–4 ·schema identity·e2adc9a3c325 -
aida-agent·packages/agent/migrations/173_add_payroll_book_members_skill_tool.sqlL1–3 ·schema identity·e2adc9a3c325 -
本轮 186:
aida-agent·packages/agent/migrations/186_background_open_roots_index.sqlL56–70 ·idx_agent_runs_background_open_roots·e2adc9a3c325;aida-agent·packages/agent/src/config/schema-metadata.tsL6–8 ·aida-config-dev-2026-10-28-conversation-turn-queue·e2adc9a3c325;aida-agent·packages/agent/src/config/schema-identity.tsL19 ·idx_agent_runs_background_open_roots·e2adc9a3c325。共享库是否已执行未核。