@zq-silk/yui 0.15.8 → 0.15.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -0
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +211 -14
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/cli/commandCatalog.js +40 -16
- package/dist/cli/interactionPolicy.js +3 -3
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +403 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/operatorCommands.js +33 -2
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActivationCommands.js +22 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +846 -155
- package/dist/commands/taskIntegrationCommands.js +16 -38
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +28 -16
- package/dist/context/taskContext.js +64 -5
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +11 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
- package/dist/controller/globalInputDelivery.js +119 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +81 -6
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +312 -382
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/kernel/builtinCapabilities.js +32 -24
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +423 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/plugins/pluginService.js +11 -3
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +72 -15
- package/dist/repository/taskWorkspaceCoordinator.js +134 -0
- package/dist/repository/taskWorkspacePreparer.js +120 -49
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/resources/projectResource.js +0 -48
- package/dist/resources/projectResourceService.js +3 -81
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentHost.js +152 -72
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +12 -1
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +29 -16
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +173 -7
- package/dist/storage/sqliteStore.js +41 -22
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -1
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/taskActivation.js +26 -0
- package/dist/task/taskActivationService.js +85 -69
- package/dist/task/taskSubmission.js +236 -0
- package/dist/web/assets/client/app.js +58 -2
- package/dist/web/assets/client/components.js +1 -0
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/assets/client/taskSurface.js +202 -7
- package/dist/web/assets/client/view.js +7 -4
- package/dist/web/assets/shell.js +23 -0
- package/dist/web/assets/styles/layout.js +1 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +135 -4
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +225 -8
- package/dist/workItem/workItem.js +14 -10
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/agent-result-consumption.md +2 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +2 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +44 -32
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -79
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +2 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +2 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +320 -217
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +2 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +41 -0
- package/docs/release-workflow.zh-CN.md +266 -0
- package/docs/roles-and-configuration.md +2 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +225 -1
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -57
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +2 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +2 -0
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +26 -0
- package/docs/testing/verification-levels.zh-CN.md +80 -0
- package/i18n/README.zh-CN.md +199 -10
- package/package.json +2 -1
- package/skills/yui-leader/SKILL.md +88 -331
- package/skills/yui-leader/references/execution.md +405 -0
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/task-plugins.md +8 -4
- package/skills/yui-operator/SKILL.md +22 -4
- package/skills/yui-runtime/SKILL.md +27 -0
- package/skills/yui-runtime/references/publication.md +20 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
<p align="right"><a href="./release-workflow.md">English</a> | <strong>简体中文</strong></p>
|
|
2
|
+
|
|
3
|
+
# 获授权的发布操作
|
|
4
|
+
|
|
5
|
+
发布工作流是一段被显式选择、获授权的外部发布效果序列——pull request、CI 确认、
|
|
6
|
+
合并、版本 tag、npm 发布、全新安装冒烟、CLI 更新、Controller 替换、Project 迁移和
|
|
7
|
+
后置验证。它是一个专用的外部效果设施,而不是 Yui 的 Task 规划或 Agent 执行模型。
|
|
8
|
+
Agent 选择一个预先声明的计划,设施从持久状态驱动该计划:每次转换都在下一次外部调用
|
|
9
|
+
之前持久化,因此崩溃、超时或被撤销的 grant 都不会让发布陷入猜测。
|
|
10
|
+
|
|
11
|
+
两个 Task 级记录族支撑它:
|
|
12
|
+
|
|
13
|
+
- **CapabilityGrant**(`capability-grant-N`)——权威。一个具名的授权者把 grant
|
|
14
|
+
限定到若干动作、参数边界、一个过期时间、一个使用次数和一个不可逆上限。
|
|
15
|
+
- **ReleaseWorkflow**(`release-workflow-N`)——计划及其进展:一个确切来源(仓库 +
|
|
16
|
+
钉住的 commit,可选一个 artifact)、一份不可变的有序步骤计划,以及每步一条持久记录。
|
|
17
|
+
|
|
18
|
+
引擎(`src/release/releaseWorkflowEngine.ts`)是一个纯库;`yui task workflow` 和
|
|
19
|
+
`yui task grant` 命令驱动它。每个外部系统都位于 `ReleaseWorkflowPorts`
|
|
20
|
+
(`src/release/releaseWorkflowPorts.ts`)之后,因此整个工作流可以用确定性的 fake
|
|
21
|
+
测试,不产生任何真实的 GitHub、npm、git、Controller 或进程副作用。
|
|
22
|
+
|
|
23
|
+
## 授权模型
|
|
24
|
+
|
|
25
|
+
每一次(再)提交一个步骤,都在外部调用**之前**通过 `checkGrant(grant, request, now)`
|
|
26
|
+
(`src/grant/capabilityGrant.ts`)。步骤 kind 就是 grant 动作:一个 grant 列出它授权的
|
|
27
|
+
步骤 kind,例如 `--action npm-publish --action version-tag`。该判定是 fail-closed
|
|
28
|
+
的——每个拒绝都带一个机器可读原因并停止这次运行:
|
|
29
|
+
|
|
30
|
+
| 原因 | 含义 |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `grant-missing` | 没有 grant 记录绑定到该工作流(引擎级)。 |
|
|
33
|
+
| `grant-revoked` | 该 grant 已被 operator 撤销。 |
|
|
34
|
+
| `grant-expired` | 墙钟已过该 grant 的 `expiresAt`。 |
|
|
35
|
+
| `grant-uses-exhausted` | 该 grant 的 `maxUses` 已被消耗。 |
|
|
36
|
+
| `grant-action-not-allowed` | 步骤 kind 不在该 grant 的动作中。 |
|
|
37
|
+
| `grant-parameter-missing` | 步骤缺少一个受约束参数。 |
|
|
38
|
+
| `grant-parameter-value-not-allowed` | 一个受约束参数的取值越界。 |
|
|
39
|
+
| `grant-irreversibility-exceeds-ceiling` | 该步骤比 grant 的上限更不可逆。 |
|
|
40
|
+
|
|
41
|
+
附加规则:
|
|
42
|
+
|
|
43
|
+
- **每次获授权提交消耗一次。** 引擎在成功判定与外部调用之间记录一次 grant 使用,
|
|
44
|
+
因此一个 `maxUses` grant 会在那次将要超额的尝试上 fail closed。
|
|
45
|
+
- **不可逆步骤需要一个已确认的前缀。** 一个标记为 `irreversible` 的步骤额外要求此前
|
|
46
|
+
每个步骤都是 `succeeded`;否则该步骤以 `prerequisite-not-confirmed` 失败并停止运行。
|
|
47
|
+
这正是让 `npm-publish` 不会跟在一个失败的 PR 之后运行的机制。
|
|
48
|
+
- **拒绝会被记录。** 当一个待处理步骤被拒绝时,引擎会启动并把该步骤失败,日志里带上
|
|
49
|
+
这次拒绝,因此 `workflow status` 能准确显示授权在哪里停下。
|
|
50
|
+
- **重新绑定。** 一个被撤销、过期或过窄的 grant 不会让工作流走进死胡同。签发一个新
|
|
51
|
+
grant 并用 `yui task workflow resume <task> <workflow> --grant <new-grant>` 恢复;
|
|
52
|
+
跨越这次重新绑定,计划、来源和所有已确认的步骤证据都不可变。
|
|
53
|
+
|
|
54
|
+
## 稳定的 Task-final Review 合同
|
|
55
|
+
|
|
56
|
+
兼容的 CLI 包更新和 Controller 替换不改变一个活动 Task 的 final-review 能力。受管
|
|
57
|
+
Session 使用普通的 `yui` 命令,兼容性由协议和存储身份检查,一个替换 Leader 呈现由
|
|
58
|
+
持久 Task 证据已确立的合同。不需要任何版本感知的 Operator 动作。Candidate 和
|
|
59
|
+
Task-final 的 ReviewRound 记录必须全部携带那唯一的合同。冲突的记录 fail closed;
|
|
60
|
+
不存在重新绑定事件、恢复命令或第二套合同状态机。
|
|
61
|
+
|
|
62
|
+
## 常驻 Agent Host 升级兼容
|
|
63
|
+
|
|
64
|
+
存活 Host 保持原 Endpoint 实现,先把准确的 Session/attempt/nativeTurn 事实写入现有
|
|
65
|
+
持久 Inbox,再联系 Controller。只有当前 Controller 解析 Run 归属、校验权限、工作区、
|
|
66
|
+
生命周期与历史关联并提交结果。Inbox 文件不代表接受;提交后才确认消费,断线或丢失
|
|
67
|
+
ACK 不会重放用户输入或模型工作。
|
|
68
|
+
|
|
69
|
+
启动事实从已兑现的 launch payload 取得准确 Run 身份,不把 Run 固定到长期 Session
|
|
70
|
+
环境。即使冻结的 Run 工作区不同于 Role 默认值,登记前的证据也能保留;后续活动和
|
|
71
|
+
终态仍只按各自的原生输入身份解析归属。
|
|
72
|
+
|
|
73
|
+
支持边界为控制协议 `yui-agent-host/v5`、事件来源协议 `yui-agent-host-events/v1`、
|
|
74
|
+
Controller RPC 版本 4。声明 `storage=controller-owned` 的 Host 不打开 Home 数据库,
|
|
75
|
+
包括进程归属、原生账号位置和执行环境校验。Home 22 声明 Inbox 的新增来源字段,
|
|
76
|
+
不改写有效历史事实与业务记录。后续版本要么保留该线协议,要么在修改存储前拒绝不兼容
|
|
77
|
+
的存活生产者。刷新 Session CLI wrapper 或新 `doctor` 成功都不能证明旧 Host 兼容。
|
|
78
|
+
|
|
79
|
+
`upgrade`、`update` 的目标版本预检及 release activation 独立检查存活 Host。
|
|
80
|
+
没有此能力的 legacy Host(包括 idle)以及兼容响应不确定的 Host 都阻止采用;
|
|
81
|
+
在既有隔离/静默交接边界再次检查,先于迁移或发布切换。检查不会 kill、替换或 reload
|
|
82
|
+
Host。先让原有工作结束并保留原始未决输入/结果证据,再由获授权的 Operator 在安全
|
|
83
|
+
边界选择 Session 替换或清理,之后重试。安装新代码不能修改已加载的 legacy Host 内存,
|
|
84
|
+
也不能回收它从未持久投递过的终态。
|
|
85
|
+
|
|
86
|
+
`task role status`、`task role list` 和 `task role session inspect` 在持久 Run 状态旁
|
|
87
|
+
显示 Host 上报观测。原生结果待落库或已知上报故障需要关注,但不代表 Provider 失败、
|
|
88
|
+
Run 已结束或业务已验收。Host 诊断只检查自己已投递文件的身份是否存在,不解析或隔离
|
|
89
|
+
其他生产者的新格式事件。
|
|
90
|
+
|
|
91
|
+
## CLI 与 Controller 发布边界
|
|
92
|
+
|
|
93
|
+
全局 `yui` 命令是稳定的用户与受管 Session 接口。对普通命令,它不跟随
|
|
94
|
+
`runtime/active-release.json`:那个指针选择的是 Controller 发布,而不是 CLI 包。这让
|
|
95
|
+
CLI、Operator Session 和 Controller 替换保持兼容,而不必把每个命令钉在一个不可变构建上。
|
|
96
|
+
|
|
97
|
+
一个源码 checkout 或以其他方式未经验证的本地 CLI 不是这个已发布接口。当 `YUI_HOME`
|
|
98
|
+
已经命名了一个活动发布时,这样的 CLI 会在打开存储之前失败,并报告它的构建/来源、
|
|
99
|
+
持久的 Home 身份以及它的调用类别。`make install-local` 仍默认使用 checkout 隔离的
|
|
100
|
+
`output/dev/home`;把那个 launcher 显式指向一个发布拥有的 Home 会被拒绝。
|
|
101
|
+
|
|
102
|
+
一个显式的 `yui release activate <release-id|build-id>` 是唯一的例外。全局 CLI 校验
|
|
103
|
+
已安装的目标发布及其匹配的冒烟回执,然后把未改动的激活参数委派给那个目标的
|
|
104
|
+
`dist/cli.js`。因此目标发布拥有完整的交接协议和超时层级。一次无目标的激活、help、
|
|
105
|
+
`--json` 以及其他每个命令都留在全局 CLI 上。激活不新增另一条普通 CLI 路由路径。
|
|
106
|
+
|
|
107
|
+
## 步骤目录
|
|
108
|
+
|
|
109
|
+
计划是一个固定、预先声明的操作子集。每个计划条目有一个 id(工作流内唯一)、一个
|
|
110
|
+
kind、可选 params,以及一个可选的不可逆级别(`none` | `reversible` | `irreversible`)。
|
|
111
|
+
|
|
112
|
+
| Kind | 外部效果 | 权威身份 |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `pr-create-or-reuse` | 创建发布 PR,或复用一个针对该 head 的开放 PR。 | `pull-request` 号 |
|
|
115
|
+
| `ci-confirm` | 读取该来源 ref 的 CI 结论;仅在 `success` 时成功。 | — |
|
|
116
|
+
| `merge` | 合并具名 PR(默认 squash)。 | — |
|
|
117
|
+
| `version-tag` | 创建并推送带注释的版本 tag。 | `git-tag` 名 |
|
|
118
|
+
| `npm-publish` | 把 tarball 发布到 registry。 | `npm-package` 版本 |
|
|
119
|
+
| `fresh-install-smoke` | 从 registry 安装并运行已发布的包。 | — |
|
|
120
|
+
| `cli-update` | 通过既有更新编排器更新 Yui CLI/Controller home。 | `controller-home` |
|
|
121
|
+
| `controller-replace` | 停止并重启 file-task Controller。 | — |
|
|
122
|
+
| `project-migrate` | 通过既有的 project 命令运行 Project 迁移。 | — |
|
|
123
|
+
| `post-verify` | 运行一个任意的验证命令。 | — |
|
|
124
|
+
|
|
125
|
+
步骤可以引用更早的证据:一个 param 值为 `$externalId:<step-id>` 时,会在运行时解析为
|
|
126
|
+
被引用步骤已确认的 external id,因此一个 `merge` 步骤可以消费 `pr` 步骤产生的 PR 号,
|
|
127
|
+
而 operator 事先并不需要知道它。对一个未确认步骤的引用会让运行失败,而不是猜测。
|
|
128
|
+
|
|
129
|
+
## 恢复与 resume 语义
|
|
130
|
+
|
|
131
|
+
一次运行总是从 **resume 游标**开始:第一个状态非终态(`succeeded` 或 `skipped`)的
|
|
132
|
+
计划步骤。没有“从头再来”——已确认的步骤绝不重跑。
|
|
133
|
+
|
|
134
|
+
因为每次状态转换都在下一次外部调用之前持久化,所以任意一点的进程退出都是可恢复的:
|
|
135
|
+
重新调用 `run`(或 `resume`),引擎就从第一个未确认步骤继续。`--max-steps <n>` 限定
|
|
136
|
+
单次运行;一次在工作流中途耗尽预算的运行返回 `budget-exhausted`,下一次调用继续。
|
|
137
|
+
|
|
138
|
+
在途步骤通过**权威身份查询**解决,绝不盲目重新提交:
|
|
139
|
+
|
|
140
|
+
- 一个留在 `running` 或 `unknown` 的步骤,先按其记录的 `externalIdentity` 查询。
|
|
141
|
+
- `exists` → 该步骤到达 `succeeded`,且**没有第二次提交**(`unknown` 被确认,
|
|
142
|
+
`running` 被完成)。
|
|
143
|
+
- `unknown` → 运行以结果 `unknown` 停止;在其命运不可知期间,该步骤绝不被重新提交。
|
|
144
|
+
- `absent` → 效果从未落地,因此该步骤被重试(一个 `running` 步骤会记录这次恢复尝试)。
|
|
145
|
+
- 一个**没有** external identity 的 `running` 步骤在记录提交结果之前就崩溃了。一个
|
|
146
|
+
不可逆步骤无论如何都通过端口查询(适配器咨询其持久幂等存储):`exists` 不经第二次
|
|
147
|
+
提交确认该步骤,`unknown` 以 `unconfirmed` 停止,只有权威的 `absent` 才恰好重试该
|
|
148
|
+
步骤一次。一个可逆步骤总是落到重试,并沿用同一个幂等键。
|
|
149
|
+
- 一次**没有** external identity 的超时把该步骤标记为 `unknown`(unconfirmed),因此
|
|
150
|
+
它绝不被盲目重新提交;在 resume 时它以 `unconfirmed` fail closed。
|
|
151
|
+
- 一个 `failed` 步骤在下一次运行时被重试;它的 `attempts` 计数和日志按尝试增长。
|
|
152
|
+
|
|
153
|
+
运行结果:`succeeded`、`failed`、`unknown`、`unauthorized`、`unconfirmed`、
|
|
154
|
+
`budget-exhausted`。每个都带一个机器可读的 `stopReason`(例如 `unknown:publish`、
|
|
155
|
+
`unauthorized:grant-revoked`、`budget-exhausted:verify`)以及该次运行尝试过的步骤 id
|
|
156
|
+
列表。
|
|
157
|
+
|
|
158
|
+
## 幂等键合同
|
|
159
|
+
|
|
160
|
+
每个步骤的幂等键在**创建时预先声明**且永不改变:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
<taskId>/<workflowId>/<stepId>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
该键被传给该步骤的每一次 `executeStep` 调用,包括在一次确认为 absent 的超时之后的
|
|
167
|
+
重试。端口合同要求 `executeStep` 在同一键下是幂等的:一次重试尝试不得产生第二次
|
|
168
|
+
副作用。引擎侧的合同更严格——它绝不为一个已标记 `unknown` 的步骤调用 `executeStep`,
|
|
169
|
+
而是按记录的身份重新查询。fake 记录每一个键,因此测试套件直接证明至多一次执行。
|
|
170
|
+
|
|
171
|
+
## Operator 指南
|
|
172
|
+
|
|
173
|
+
Session 权威依据当前持久绑定检查。Telemetry 按 Role/AgentRun 分组,进程 owner 使用
|
|
174
|
+
PID/start 身份。存储变更遵循[唯一的显式升级边界](sqlite-control-plane-design.zh-CN.md);
|
|
175
|
+
普通命令绝不改写 Home schema。
|
|
176
|
+
|
|
177
|
+
grant 的签发与撤销是不可逆权威操作。它们需要当前已登记的全局 Operator 对话。它的
|
|
178
|
+
原生 session ID 必须与持久的活动 session 绑定匹配:Codex 命令在存在时使用
|
|
179
|
+
`CODEX_THREAD_ID`,否则使用 `YUI_NATIVE_SESSION_ID`;Claude 使用 `YUI_NATIVE_SESSION_ID`。
|
|
180
|
+
Host generation 和启动时的 Agent 标签不是调用者身份。通过另一个入口恢复同一段对话
|
|
181
|
+
不撤销其权威。一个未登记、被替换或已结束的对话没有这种权威。一个受管的 Task Agent
|
|
182
|
+
不能自签发或自撤销 grant,清空子进程环境也不赋予用户权威。被记录的授权者/撤销者
|
|
183
|
+
绑定到那个 Operator session(`operator:<agent-id>`);不存在可伪造的 `--granter`/`--by`
|
|
184
|
+
标签。
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
# 1. Operator session 为发布链签发权威。
|
|
188
|
+
yui task grant issue task-15 \
|
|
189
|
+
--action pr-create-or-reuse --action npm-publish --action post-verify \
|
|
190
|
+
--irreversibility-ceiling irreversible
|
|
191
|
+
|
|
192
|
+
# 2. 针对确切来源和预先声明的计划创建工作流。
|
|
193
|
+
# npm-publish 步骤需要一个内容寻址的来源 artifact:不可变的工作流来源以后
|
|
194
|
+
# 永远无法再获得它,因此没有 --source-artifact 的计划在创建时即被拒绝。
|
|
195
|
+
yui task workflow create task-15 \
|
|
196
|
+
--grant capability-grant-1 \
|
|
197
|
+
--source-repo acme/widget --source-commit abc1234deadbeef0000000000000000000000000 \
|
|
198
|
+
--source-artifact widget-1.0.0.tgz@sha512-<base64-integrity> \
|
|
199
|
+
--step pr:pr-create-or-reuse \
|
|
200
|
+
--step publish:npm-publish --step-irreversibility publish=irreversible \
|
|
201
|
+
--step-param publish:tarball=./dist/widget-1.0.0.tgz \
|
|
202
|
+
--step verify:post-verify --step-param verify:command='yui --version'
|
|
203
|
+
|
|
204
|
+
# 3. 运行(或 resume)并检查。
|
|
205
|
+
yui task workflow run task-15 release-workflow-1
|
|
206
|
+
yui task workflow resume task-15 release-workflow-1 [--grant capability-grant-2] [--max-steps 1]
|
|
207
|
+
yui task workflow status task-15 release-workflow-1
|
|
208
|
+
|
|
209
|
+
# 4. 随时撤销权威;下一个步骤以 unauthorized 停止。
|
|
210
|
+
yui task grant revoke task-15 capability-grant-1
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`workflow status` 渲染每个步骤的状态、尝试次数和已确认的 external id,因此 operator
|
|
214
|
+
能准确看到一次发布在哪里停下以及为什么。
|
|
215
|
+
|
|
216
|
+
## 真实资源边界
|
|
217
|
+
|
|
218
|
+
真实执行只通过 `yui` CLI 发生,它接上真实适配器(`createReleaseWorkflowPorts`)。该
|
|
219
|
+
适配器是既有原子操作——`gh`、`npm`、`git`、CLI 更新编排器、Controller stop/restart 和
|
|
220
|
+
`project migrate`——之上的一层薄壳,并且只在一个人类授权者签发了对每个步骤都通过
|
|
221
|
+
`checkGrant` 的显式 CapabilityGrant 时才运行。一个本地测试请求绝不替代那份权威。
|
|
222
|
+
|
|
223
|
+
由 tag 触发的 `publish.yml` 工作流是唯一维护的发布冒烟。它复用那个通过了 core CI 的
|
|
224
|
+
确切 commit,只增加发布所需的产物装配、全新安装和 provenance 检查。
|
|
225
|
+
|
|
226
|
+
该工作流通过 npm Trusted Publishing(OIDC)认证,因此发布身份存在于 tag 之外的两处:
|
|
227
|
+
`repository`、`bugs` 和 `homepage` 由 `assemble-runtime-package.mjs` 从源 `package.json`
|
|
228
|
+
逐字复制进已发布 manifest,而该包的 npm Trusted Publisher 条目点明 GitHub owner、
|
|
229
|
+
仓库、工作流文件和环境。npm 在接受 provenance 之前会区分大小写地将 `repository.url`
|
|
230
|
+
与正在构建的仓库比较。因此重命名或转移 GitHub 仓库时,必须连同重命名一起更新这些 URL
|
|
231
|
+
和 npm Trusted Publisher 条目;否则下一个 tag 会走到 `npm publish` 并在那里失败——此时
|
|
232
|
+
tag 和已过门的构建都已成功。
|
|
233
|
+
|
|
234
|
+
## 适配器安全加固
|
|
235
|
+
|
|
236
|
+
真实适配器(`createReleaseWorkflowPorts`)在引擎的 grant 检查之外再施加额外防护:
|
|
237
|
+
|
|
238
|
+
- **Tarball 选项注入。** 一个看起来像选项的 tarball 路径(以 `-` 开头)在任何子进程
|
|
239
|
+
——`tar -xOf` manifest 检视和 `npm publish`——看到它之前就被拒绝,因此一个精心构造的
|
|
240
|
+
路径永远不会被当作 flag 解释。
|
|
241
|
+
- **Tarball TOCTOU。** 在校验冻结的 `source.artifact.integrity` 之后,被校验的字节被
|
|
242
|
+
快照到一个工作流私有、只读的临时文件。`tar -xOf` manifest 检视和 `npm publish` 都
|
|
243
|
+
读取该快照,而不是活的 tarball 路径,因此校验之后对原文件的替换不能改变所发布的内容。
|
|
244
|
+
步骤完成时移除该快照。
|
|
245
|
+
- **钉住的外部命令。** 适配器在构造时通过 `resolveExecutable` 把它 shell 调用的外部
|
|
246
|
+
命令(`gh`、`git`、`npm`、`tar`、`sh`)解析为绝对路径,只走一次调用者的 `PATH`。每次
|
|
247
|
+
子进程调用都使用解析后的路径,因此之后的 `PATH` 变化(或被操纵的工作目录)不能把一次
|
|
248
|
+
发布效果重定向到另一个二进制。一个无法解析的命令返回一个合成失败(exit 127),不调用
|
|
249
|
+
任何二进制。
|
|
250
|
+
- **钉住的 cli-update 激活目标。** 在不可逆的更新效果之前,适配器把确切的激活目标——
|
|
251
|
+
Home 加上全局 npm 前缀(`bin/yui`)——持久化到 Home 下的一个持久文件
|
|
252
|
+
(`release/cli-update-identity/<idempotency-key>.json`)。一次硬退出的恢复查询(一个
|
|
253
|
+
没有记录身份的步骤)读取这个文件并调用那个钉住的目标;如果该文件不存在(进程在预效果
|
|
254
|
+
持久化之前退出),查询返回 `unknown`,而不是从 resume 调用者的 `npm prefix --global`
|
|
255
|
+
或 `PATH` 推导目标,因此 resume 环境中的另一个安装不能替这个步骤背书。
|
|
256
|
+
- **Controller 生命周期校验。** 一次 `cli-update` 恢复查询会证明替换后的 Controller
|
|
257
|
+
确实拥有目标 Home:它运行 `yui --json controller status`(`YUI_HOME` 钉在记录的 Home
|
|
258
|
+
上),并要求一个 `current` controller 资源,其 `yuiHome` 解析到那个 Home,然后运行
|
|
259
|
+
`yui --json controller identity`,并要求已认证的 Controller 身份与已激活的产物匹配:
|
|
260
|
+
Node.js 可执行路径、由钉住的全局二进制派生的确切 Controller 入口点,以及包版本。仅有
|
|
261
|
+
二进制健康(doctor、`--version`)绝不确认这次交接,任何不可证明的状态都返回 `unknown`。
|
|
262
|
+
这适用于带身份的查询和硬退出查询(一个没有记录身份的步骤)两者。
|
|
263
|
+
- **npm integrity 比较。** 一次 `npm-publish` 恢复查询不止步于已发布版本:它通过
|
|
264
|
+
`npm view <pkg>@<version> dist.integrity` 取 `dist.integrity`,并与冻结的
|
|
265
|
+
`source.artifact.integrity` 逐字节比较。匹配则确认该步骤;同一版本但字节不同是一个
|
|
266
|
+
冲突,返回 `unknown`(绝不确认,绝不重新发布);一个缺失的版本是 `absent`。
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<p align="right"><a href="./roles-and-configuration.md">English</a> | <strong>简体中文</strong></p>
|
|
2
|
+
|
|
3
|
+
# Role、Profile 与执行配置
|
|
4
|
+
|
|
5
|
+
## 职责
|
|
6
|
+
|
|
7
|
+
Agent 选择一个执行组件、连接方案和启动环境。Role 选择一个活动的 Agent 绑定和
|
|
8
|
+
可移植行为。每个绑定保留独立的运行时选项。一个 Role 可以持有多个绑定,而不产生
|
|
9
|
+
并行写者,也不把一个绑定的凭据/配置共享给另一个。
|
|
10
|
+
|
|
11
|
+
Task Role 是 Task 局部的;global Role 提供已配置的默认值和全局对话。Role
|
|
12
|
+
身份/配置不是可写的运行时状态。Session 与 Provider 观察描述实际活动。
|
|
13
|
+
|
|
14
|
+
一次显式的 Task-final Review 使用既有的 Task 局部 Reviewer Role,不要求存在同名
|
|
15
|
+
的 global Role。只有当请求的 Task Role 不存在时,global Role 才作为创建模板。
|
|
16
|
+
可用性、producer 分离和冻结的审查候选仍然适用。
|
|
17
|
+
|
|
18
|
+
## Profile
|
|
19
|
+
|
|
20
|
+
Agent Profile 把可移植行为(指令、Skill 和访问意图)与运行时意图组合在一起。
|
|
21
|
+
运行时要么沿用当前 Global Worker 绑定,要么显式选择一个 Agent 并可选 model 和
|
|
22
|
+
effort。`config profile reset` 提供 `worker`、`explorer`、`implementer` 和
|
|
23
|
+
`reviewer`。
|
|
24
|
+
|
|
25
|
+
从 Profile 创建 Task Role 会冻结其解析后的行为和绑定。之后对 Profile 或 Global
|
|
26
|
+
Worker 的编辑不会改写既有的 Task Role。重新套用一个 Profile 是一次显式配置变更。
|
|
27
|
+
所选 Agent 必须与目标绑定匹配;显式的 Role 选项覆盖对应的模板字段。Profile
|
|
28
|
+
不是 Session、工作区 owner 或资源 grant。
|
|
29
|
+
|
|
30
|
+
原生子代继承其父 Agent 和权限。Profile 可以引导它们的行为;model/effort 覆盖
|
|
31
|
+
需要真实的原生工具支持。它们不获得 Yui Role、独立 Assignment 或更大范围。
|
|
32
|
+
|
|
33
|
+
## 期望、生效与观察
|
|
34
|
+
|
|
35
|
+
期望设置是下一次启动的意图。AgentRun 和 Session 捕获生效启动:Agent/组件、
|
|
36
|
+
协议、model、effort、权限策略、工作区/环境、Role 上下文以及 planning/delivery
|
|
37
|
+
权限。
|
|
38
|
+
|
|
39
|
+
对运行中配置的检查单独报告 Agent 实际声明的内容。unsupported 和 unknown 都是
|
|
40
|
+
显式的;一个被接受的 setter 若没有回报当前值,并不算已观察到的匹配。读取配置
|
|
41
|
+
不会修改 Agent 以让观察与期望一致。
|
|
42
|
+
|
|
43
|
+
Worker 绑定变更保留活动 Assignment 的 Agent 和生效快照;之后的显式派发使用当前
|
|
44
|
+
选择。Leader 替换撤销上一条管理入口,但不改写 Worker Assignment。更改期望配置
|
|
45
|
+
不会热改原生 Session。一次显式的 `task role session new` 请求会在选择新 Session
|
|
46
|
+
之前处理旧运行时清理;它不要求先手动结算 Run 状态。一个有用的 Session 可以复用,
|
|
47
|
+
但它绝不是 Task 上下文的唯一持有者。
|
|
48
|
+
|
|
49
|
+
## 权限与 Project 上下文
|
|
50
|
+
|
|
51
|
+
Provider 权限策略、Profile 访问意图和 Project 写范围是不同的合同。Provider 旁路
|
|
52
|
+
不授予对另一个 Project 的写入。受管工作区 owner、精确 Assignment 和资源 grant
|
|
53
|
+
落实 Yui 操作;宽泛的原生权限不是 OS 沙箱。
|
|
54
|
+
|
|
55
|
+
Yui 提供其通用 Role Skill 和 Context 指针。Project Skill 仍是由 Agent 原生发现的
|
|
56
|
+
普通 Project 文件。Project Knowledge 维护在 `YUI_HOME` 下;把仓库材料复制进 prompt
|
|
57
|
+
并不使其成为权威 Knowledge。
|
|
58
|
+
|
|
59
|
+
## 原生认证
|
|
60
|
+
|
|
61
|
+
账号配置比 Session 活得更久。Yui 保留 `HOME` 和所选的 `CLAUDE_CONFIG_DIR`;
|
|
62
|
+
新建/恢复的 Session 不会复制、清除或伪造原生登录、key 批准或 onboarding 记录。
|
|
63
|
+
|
|
64
|
+
对 Claude Code,标准的 API-key、base-URL、bearer/OAuth、model-alias 以及原生
|
|
65
|
+
provider 选择相关环境变量只转发给 Claude。这些值留在 Controller 可替换的运行时
|
|
66
|
+
环境和子进程中,不进入 Task/Role 记录。取消某个来源并刷新 Controller 环境,即可
|
|
67
|
+
在后续启动中移除它。其他自定义凭据变量仍使用显式的 Agent 环境绑定或原生用户设置;
|
|
68
|
+
Yui 不继承整个 shell 环境,也不推断云凭据。
|
|
69
|
+
|
|
70
|
+
Claude 自身加载原生设置,并按其生效配置在 API key、既有 helper、登录凭据、profile
|
|
71
|
+
和云认证之间做选择。Yui 不注入 `apiKeyHelper`、不复制凭据文件,也不覆盖原生认证
|
|
72
|
+
优先级。显式的 `--settings` 路径和 settings 来源选择被原样透传。
|
|
73
|
+
|
|
74
|
+
全新的原生配置仍可能需要 Claude 的初始化、key、工作区和安全确认,包括访问初始化
|
|
75
|
+
服务。隔离 `YUI_HOME` 或替换 Session 都不要求一个全新的原生账号目录。受管的 Task
|
|
76
|
+
执行使用 Claude 的非交互 stream-json 路径,并沿用同样的原生配置归属。
|
|
77
|
+
|
|
78
|
+
## 命令
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
yui config agent capabilities <agent-id>
|
|
82
|
+
yui config role show <global-role>
|
|
83
|
+
yui config profile show <profile>
|
|
84
|
+
yui task role add <task> <role> --profile <profile>
|
|
85
|
+
yui task role show <task> <role>
|
|
86
|
+
yui task role update <task> <role> --environment <preparation-id>
|
|
87
|
+
yui task role update <task> <role> --managed-environment
|
|
88
|
+
yui task role session inspect <task> <role>
|
|
89
|
+
yui task role session new <task> <role> --reason "<why a fresh Session is useful>"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
创建 Role 时,显式的 Agent 设置需要 `--agent`。更新时,省略 `--agent` 针对活动
|
|
93
|
+
绑定;一个具名绑定会被更新但不被激活。`task role bind` 更改选择。在更改期望设置
|
|
94
|
+
之前,活动 Session 需要该命令的显式确认。
|
|
95
|
+
|
|
96
|
+
关于原生配置和实现限制,参见 [Provider Runtime](provider-runtime.zh-CN.md)。
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<p align="right"><strong>English</strong> | <a href="./sqlite-control-plane-design.zh-CN.md">简体中文</a></p>
|
|
2
|
+
|
|
1
3
|
# SQLite control-plane storage
|
|
2
4
|
|
|
3
5
|
Yui has one authoritative product Store: `YUI_HOME/yui.db` in WAL mode. The
|
|
@@ -67,10 +69,232 @@ Every persistent schema or payload change appends one immutable, contiguous
|
|
|
67
69
|
storage migration. The CLI publishes both `storageVersion` and
|
|
68
70
|
`minimumStorageVersion`; every valid Home in that inclusive range can upgrade
|
|
69
71
|
directly to the current version without installing intermediate releases.
|
|
70
|
-
The current source declares storage version **
|
|
72
|
+
The current source declares storage version **25**, with minimum supported
|
|
71
73
|
migration version **1**, in `src/storage/storageVersions.ts`. Homes below that
|
|
72
74
|
floor are not migration inputs and remain untouched.
|
|
73
75
|
The target binary's `upgrade --update-preflight` and `--update-apply` result
|
|
74
76
|
shapes and parent-owned handover-lock proof remain backward compatible with
|
|
75
77
|
every updater released from storage version 1 onward, so an old source CLI can
|
|
76
78
|
still drive a much newer target's complete migration chain.
|
|
79
|
+
|
|
80
|
+
## Unified Home layout
|
|
81
|
+
|
|
82
|
+
Every Yui self-managed directory lives under the single canonical `YUI_HOME`
|
|
83
|
+
(default `~/.yui`; an explicit `YUI_HOME` is honoured verbatim). `YUI_HOME` is
|
|
84
|
+
never inferred from the current working directory and never substituted with a
|
|
85
|
+
username. `src/storage/homeLayout.ts` is the one authority that derives each
|
|
86
|
+
managed root from Home:
|
|
87
|
+
|
|
88
|
+
| Root | Path | Holds |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| Managed worktrees | `<home>/workspaces/tasks/<taskId>/<owner>/<projectDirectory>` | Actual Task/WorkItem/Review/Integration Git directories, addressed by the bound Project directory. Owners are `main`, `work-items/<id>`, `reviews/<id>`, `integrations/<id>` and `execution-lanes/<group>/<lane>`. |
|
|
91
|
+
| Read-only context views | Within the same owner directory | Regenerable symlinks to read-only Project context only; writable entries are actual Git directories, not links. |
|
|
92
|
+
| Global Role workspace | `<home>/workspaces/global` | Default cwd for Yui-auto-created Global Roles (the `yui setup` Operator/Leader and ad-hoc Global Roles added without an explicit `--workspace`). A plain cwd, not a managed Git workspace. |
|
|
93
|
+
| Task provider runtimes | `<home>/runtime/task-runtimes` | Task provider data/cache/tmp; also the planning cwd at `…/planning/<taskId>`. |
|
|
94
|
+
| Integration runtimes | `<home>/runtime/integration-runtimes` | The integration check's provider data/cache/tmp (a separate partition from Task runtimes). |
|
|
95
|
+
| Update staging | `<home>/runtime/update-staging` | `yui update`'s side-by-side package install (an upgrade artifact). |
|
|
96
|
+
| Release workflow scratch | `<home>/runtime/release-workflow` | The release workflow's smoke-install dir and verified publish-snapshot tarball (release artifacts). |
|
|
97
|
+
| Storage backups | `<home>/backups` | Pre-upgrade DB backups (the fenced upgrade's rollback anchor). |
|
|
98
|
+
|
|
99
|
+
Published migrations 1–23 remain unchanged, including Task artifacts in local
|
|
100
|
+
Git (19), Integration continuation (20), force-archive evidence (21), and
|
|
101
|
+
Controller-owned Host ingress (22), and unified message input control (23).
|
|
102
|
+
The two offline layout steps are now 23→24 (`unify-home-layout`) and
|
|
103
|
+
24→25 (`collapse-worktree-layout`). Version 24's
|
|
104
|
+
`workspaces/worktree` directory is an intermediate layout, not a second live
|
|
105
|
+
root at version 25. A single upgrade applies the full pending chain.
|
|
106
|
+
|
|
107
|
+
Stop this Home's writers and take a backup before upgrading. The layout steps
|
|
108
|
+
copy and verify the registered Git trees, repair only the copies' links, and
|
|
109
|
+
preserve old sources for manual recovery. Version 25 replaces registered
|
|
110
|
+
Task-view symlinks with real writable directories; unrelated Task scratch is
|
|
111
|
+
retained. Read-only context remains a view and can be promoted to a writable
|
|
112
|
+
worktree when WorkItem scope expands. Do not delete the old sources until the
|
|
113
|
+
new layout is verified; a failed upgrade requires manual residue cleanup and
|
|
114
|
+
backup recovery, not automatic resume.
|
|
115
|
+
|
|
116
|
+
Both runtime partitions (`runtime/task-runtimes`, `runtime/integration-runtimes`)
|
|
117
|
+
are the ONLY Home subtrees a provider runtime root is allowed to overlap; a
|
|
118
|
+
runtime root overlapping any other part of Home (the database, `workspaces/`,
|
|
119
|
+
`projects/`) is still rejected by `assertTaskRuntimeIsolationPreflight`, so
|
|
120
|
+
unifying the root does not weaken control-data or cross-owner isolation.
|
|
121
|
+
|
|
122
|
+
`defaultWorkspace` is a user-facing cwd for external Project input only; it is
|
|
123
|
+
**not** a second authority for internal managed paths, and is intentionally not
|
|
124
|
+
an input to `homeLayout.ts`. A Yui-auto-created Global Role that carries no
|
|
125
|
+
user-chosen cwd no longer falls back to it (or to `process.cwd()`): `yui setup`'s
|
|
126
|
+
built-in Operator/Leader and `yui role add` without `--workspace` now default to
|
|
127
|
+
the Home-internal `managedGlobalRoleWorkspace(home)` (`<home>/workspaces/global`),
|
|
128
|
+
and `setup` no longer fabricates an external Home-sibling `workspace/` — a
|
|
129
|
+
`default-workspace` is persisted only if the user configured one. A user who
|
|
130
|
+
*names* an external directory (explicit `--workspace`, or a configured
|
|
131
|
+
`default-workspace`) keeps external-resource semantics; the outside-Home guard
|
|
132
|
+
still applies to it. The "planning/global cwd" that criterion 1 places under Home
|
|
133
|
+
is thus both the *disposable runtime cwd Yui materializes itself* — the Draft
|
|
134
|
+
planning cwd (`planningRuntimeCwd`, under `runtime/task-runtimes/planning`) — and
|
|
135
|
+
the auto-created Global Role cwd above; only an operator's *explicitly named*
|
|
136
|
+
external directory stays outside by design.
|
|
137
|
+
|
|
138
|
+
Only genuine short-path IPC socket ENDPOINTS remain outside Home, and only
|
|
139
|
+
because a Unix-domain `sockaddr_un` path has a small fixed length budget that a
|
|
140
|
+
deep Home path would exceed. Each is a single socket path, never a data/cache/tmp
|
|
141
|
+
root:
|
|
142
|
+
|
|
143
|
+
- the Controller socket (`/tmp/yui-<uid>/<homeId>.sock`),
|
|
144
|
+
- the tmux server socket (`/tmp/tmux-<uid>` via the tmux namespace),
|
|
145
|
+
- the Agent Host socket (`/tmp/yui-<uid>/agent-host/…sock`), and
|
|
146
|
+
- the integration check's tmux socket dir (`/tmp/yi-<uid>-<digest>`), bound only
|
|
147
|
+
into `TMUX_TMPDIR`.
|
|
148
|
+
|
|
149
|
+
The integration check's ordinary runtime state is **not** an exception: its
|
|
150
|
+
provider data, cache, and temp roots live in the Home partition above
|
|
151
|
+
(`runtime/integration-runtimes`); `TMPDIR`/`TMP`/`TEMP` point there, and only
|
|
152
|
+
`TMUX_TMPDIR` is redirected to the short `/tmp` socket dir.
|
|
153
|
+
|
|
154
|
+
## Migration 23 → 24: unify managed paths under Home
|
|
155
|
+
|
|
156
|
+
Historically the managed worktrees lived under the out-of-Home
|
|
157
|
+
`defaultWorkspace` (`<ws>/worktree`, `<ws>/tasks`) and the provider runtimes
|
|
158
|
+
under a string-built Home sibling (`<home>.task-runtimes`). The one forward
|
|
159
|
+
migration `unify-home-layout` (`src/storage/migrations/unifyHomeLayout.ts`)
|
|
160
|
+
brings that content under Home and rewrites the persisted absolute pointers the
|
|
161
|
+
runtime dereferences as live, without re-cloning Git content or renaming the
|
|
162
|
+
path-independent Git refs. It runs as the migration's `migrateData` step inside
|
|
163
|
+
the upgrade transaction, so schema and data advance atomically or roll back
|
|
164
|
+
together.
|
|
165
|
+
|
|
166
|
+
**Exactly one tree is physically relocated: the managed Git worktree tree.** It
|
|
167
|
+
is the sole subtree that holds durable, non-regenerable content (committed **and**
|
|
168
|
+
uncommitted work), so it alone is copied on disk. Everything else that "moves"
|
|
169
|
+
moves only by pointer:
|
|
170
|
+
|
|
171
|
+
- the per-Task symlink views (`<ws>/tasks`) are regenerable — the pointer is
|
|
172
|
+
rewritten and `ensureWorkspaceView` rebuilds the links at the next launch;
|
|
173
|
+
- the provider runtimes (`<home>.task-runtimes`) are disposable — the pointer is
|
|
174
|
+
rewritten and the roots are recreated at the next launch.
|
|
175
|
+
|
|
176
|
+
The worktree copy is **non-destructive and verified** (see *Recovery and
|
|
177
|
+
rollback*): the source is copied (never renamed away), the replica's content
|
|
178
|
+
digest is checked against the source, and only a verified replica is atomically
|
|
179
|
+
published. The original worktree tree is **preserved** as the rollback anchor;
|
|
180
|
+
removing it is a later, authorized, post-restart cleanup step, never part of this
|
|
181
|
+
transaction.
|
|
182
|
+
|
|
183
|
+
The pointer rewrite is **surgical, not a table sweep** — only records the runtime
|
|
184
|
+
treats as live launch pointers are touched:
|
|
185
|
+
|
|
186
|
+
- `managed_workspaces` — the authoritative registry (`path` column, payload
|
|
187
|
+
`root`, every `entries[].path`). Every surviving row is live (dispositioned
|
|
188
|
+
rows are deleted at cleanup).
|
|
189
|
+
- active (`status='active'`) `turns` — **both** `run.effective.workspace` (the
|
|
190
|
+
actual OS launch cwd source) and the `run.workspace` mirror, rewritten together
|
|
191
|
+
because `validateRun` requires them to stay identical; a run's
|
|
192
|
+
`.result.systemEvidence.workspaceSnapshot` is frozen Git evidence and is left
|
|
193
|
+
byte-for-byte intact.
|
|
194
|
+
- `role_session_sets` / `global_role_session_sets` — each live session's
|
|
195
|
+
`effective.workspace` in the `sessions` map; terminal sessions in `history` are
|
|
196
|
+
preserved.
|
|
197
|
+
- `review_rounds` — the mirrored workspace (only while its `managed_workspaces`
|
|
198
|
+
owner row still exists) and each OPEN execution lane; an orphaned mirror or a
|
|
199
|
+
terminal lane is frozen evidence and is preserved.
|
|
200
|
+
- `work_items` — each OPEN execution lane inside `executionGroups`; candidate
|
|
201
|
+
snapshots (`work_item_candidates`) are frozen and preserved.
|
|
202
|
+
- `task_roles.workspace` — the live launch cwd, including a Draft's planning Role
|
|
203
|
+
under the old runtime sibling (never self-healed until activation).
|
|
204
|
+
- `task_records.cwd` — self-heals on the next `prepareTaskWorkspace`, but is
|
|
205
|
+
rewritten defensively to close the stale-read window.
|
|
206
|
+
|
|
207
|
+
Everything else is preserved on purpose: `context_snapshots`, terminal `turns`
|
|
208
|
+
(with their system evidence), terminal sessions, `work_item_candidates`, terminal
|
|
209
|
+
execution lanes, terminal `durable_jobs`, `events`, and reports are frozen
|
|
210
|
+
history. `resource_registry` is re-discovered from disk; `projects.path` is an
|
|
211
|
+
external, user-owned checkout.
|
|
212
|
+
|
|
213
|
+
The migration is applied **offline** and is **fail-closed and pre-checkable**.
|
|
214
|
+
It is run by the standalone `yui upgrade` boundary AFTER the operator has stopped
|
|
215
|
+
this Home's Controller, Agent Host, and any execution/Job writers; it does not
|
|
216
|
+
orchestrate that shutdown, coordinate an online write-stop, or migrate a live
|
|
217
|
+
Session. It keeps only the minimal preconditions it can implement directly:
|
|
218
|
+
|
|
219
|
+
- It **refuses** if a queued or running `durable_jobs` step is bound to a tree
|
|
220
|
+
about to relocate. A durable Job's runner is detached and could outlive an
|
|
221
|
+
incompletely stopped Controller, so moving that tree would risk an in-flight
|
|
222
|
+
silent move; this is the one residual runtime signal the offline migration
|
|
223
|
+
still guards. Let the Job drain or cancel it, then re-run the upgrade.
|
|
224
|
+
(`active_turns` is steady state, not an in-flight signal, and is deliberately
|
|
225
|
+
not consulted.)
|
|
226
|
+
- It **refuses** if a relocation target already exists at all — it is either a
|
|
227
|
+
foreign directory or residue from a failed prior run, and the offline migration
|
|
228
|
+
never adopts a pre-existing target. Confirm the source is intact, then move or
|
|
229
|
+
remove the target and re-run the upgrade.
|
|
230
|
+
- Every refusal is surfaced as a **collected, read-only pre-check**: `yui
|
|
231
|
+
upgrade --dry-run` and the updater's `--update-preflight` run the same plan and
|
|
232
|
+
the same blocking conditions execute would throw on, opening the DB read-only
|
|
233
|
+
and reporting each independent blocker as `{reason, detail}` (blocked outcome)
|
|
234
|
+
without mutating the Home — a genuine pre-check, not a best-effort guess.
|
|
235
|
+
- A Home already in the unified layout (or a fresh Home with nothing to relocate)
|
|
236
|
+
is a **no-op**.
|
|
237
|
+
|
|
238
|
+
### Recovery and rollback
|
|
239
|
+
|
|
240
|
+
The migration keeps **no recovery manifest and no resumable state machine** — it
|
|
241
|
+
is a one-time offline transform, not an interruptible online orchestration. The
|
|
242
|
+
worktree relocation is **copy → digest-verify → atomic-publish → preserve-source**:
|
|
243
|
+
|
|
244
|
+
1. the relocation target must not already exist; a pre-existing target is refused
|
|
245
|
+
up front (foreign directory or failed-run residue — never adopted);
|
|
246
|
+
2. the source is copied into a same-filesystem staging dir (`<to>.incoming`),
|
|
247
|
+
never renamed away;
|
|
248
|
+
3. a content-addressed inventory digest of the replica is compared to the source
|
|
249
|
+
— a mismatch deletes the staging copy and aborts (nothing published, source
|
|
250
|
+
intact);
|
|
251
|
+
4. only a verified replica is `rename`d into the final target (atomic on one
|
|
252
|
+
filesystem);
|
|
253
|
+
5. the original source tree is left in place as the rollback anchor.
|
|
254
|
+
|
|
255
|
+
There is **no automatic idempotent recovery**. Because a pre-existing target is
|
|
256
|
+
always refused, a run interrupted after a partial publish does not silently
|
|
257
|
+
resume or adopt the partial tree on the next attempt: the operator inspects the
|
|
258
|
+
preserved source, removes the incomplete target (and any `<to>.incoming`
|
|
259
|
+
staging), and re-runs the upgrade from a clean state. The `--dry-run` /
|
|
260
|
+
`--update-preflight` pre-check surfaces exactly this `target-conflict` before the
|
|
261
|
+
apply transaction is entered, so the residue is reported, not discovered
|
|
262
|
+
mid-migration.
|
|
263
|
+
|
|
264
|
+
After the copy, the worktrees are reconnected. `git worktree repair` chases the
|
|
265
|
+
absolute pointer files inside a worktree, so running it on a verbatim copy whose
|
|
266
|
+
pointers still address the OLD source would rewrite the OLD source's `.git`
|
|
267
|
+
files and corrupt the rollback anchor. The migration therefore **relinks first**:
|
|
268
|
+
it deterministically repoints, in the NEW copy only, the two cross-reference
|
|
269
|
+
pointer files (a linked worktree's `.git` stub and each
|
|
270
|
+
`main/.git/worktrees/<name>/gitdir`) from OLD to NEW, and only THEN runs `git
|
|
271
|
+
worktree repair` from each main clone at its new path as a belt-and-braces
|
|
272
|
+
reconciliation now confined to the new tree. This keeps the preserved source a
|
|
273
|
+
fully independent, working Git: its `.git` is byte-for-byte unchanged and it
|
|
274
|
+
still resolves HEAD/index/status after the migration (verified empirically on a
|
|
275
|
+
private disposable Home). **A repair failure is fatal** — it aborts the migration
|
|
276
|
+
so the transaction rolls back rather than advancing the version over unrepaired
|
|
277
|
+
worktrees.
|
|
278
|
+
|
|
279
|
+
Because the data step runs inside the upgrade transaction, any throw rolls the
|
|
280
|
+
schema back to its original version; the fenced upgrade orchestrator additionally takes a
|
|
281
|
+
`database.backup()` and restores it on failure. Recovery from a failed run is
|
|
282
|
+
**manual, not automatic**: because the source is never removed and the copy is
|
|
283
|
+
digest-verified before publish, the preserved source is always intact, so the
|
|
284
|
+
operator clears any partial target and re-runs the upgrade. No re-run can lose or
|
|
285
|
+
corrupt the original content, but the tool does not itself resume an interrupted
|
|
286
|
+
move set.
|
|
287
|
+
|
|
288
|
+
**Old-source cleanup** is intentionally deferred and out of band: after a
|
|
289
|
+
successful upgrade the old external `worktree`, `tasks`, and `<home>.task-runtimes`
|
|
290
|
+
roots are left **in place** (not emptied) until an operator-authorized cleanup
|
|
291
|
+
removes them. This keeps a full rollback anchor available across the first
|
|
292
|
+
restart.
|
|
293
|
+
|
|
294
|
+
**Rollback limits:** once the Controller restarts against the unified layout and
|
|
295
|
+
begins writing new records under Home, restoring the pre-upgrade DB backup no
|
|
296
|
+
longer matches the newly written on-disk state. Until that first post-upgrade
|
|
297
|
+
write, the preserved old source plus the DB backup are a complete rollback pair;
|
|
298
|
+
after it, the supported recovery is forward (the layout is already unified), not a
|
|
299
|
+
downgrade to the split layout. Verify an upgrade only on a private, disposable
|
|
300
|
+
Home before applying it to a shared environment.
|