ai-delivery-workflow 0.7.0 → 0.7.1
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.
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
当前生产图是 `development-seven-node`:`00-bootstrap -> 01-product-shaping -> 02-solution-design -> 03-delivery-readiness -> 04-implementation -> 05-candidate-assurance -> 06-version-closeout`。`R00-release-request` 至 `R10-release-archive` 继续允许写入。
|
|
4
4
|
|
|
5
|
-
正式状态写入会先完整暂存权威事件分片、当前视图和 JSONL 事件索引,持久化 prepared journal,再提交各目标并写入 committed marker;同步异常时恢复原文件,进程中断后由下一次受锁 mutation 或 `rebuild` 幂等恢复。`inspect`、`verify`、Bootstrap 诊断与 Workflow Manager 正式状态读取期间获取与 mutation 共用的 formal-state lock
|
|
5
|
+
正式状态写入会先完整暂存权威事件分片、当前视图和 JSONL 事件索引,持久化 prepared journal,再提交各目标并写入 committed marker;同步异常时恢复原文件,进程中断后由下一次受锁 mutation 或 `rebuild` 幂等恢复。`inspect`、`verify`、Bootstrap 诊断与 Workflow Manager 正式状态读取期间获取与 mutation 共用的 formal-state lock;只读调用方只对能够解析且确认 `pid` 仍存活的活动 writer 做有限等待,以避免并发读取观察到混合 revision;未知格式锁、失联 writer、其他 reader 或未恢复 journal 均立即 fail-closed。只读调用方不得接管死锁或执行事务恢复。日常 `verify` 不遍历历史分片,只校验当前视图、JSONL 索引、物料和状态不变量;显式 `verify --history` 还检查全部事件分片的连续性、元数据、记录 checksum、schema 2 checksum 链以及最新 replay 结果与全局视图的一致性。`rebuild` 按 revision replay 每个 stream,并按确定顺序重建 JSONL 索引。
|
|
6
6
|
|
|
7
7
|
## 1. 权威源码与安装结果
|
|
8
8
|
|
|
@@ -135,7 +135,7 @@ npm run audit:verify
|
|
|
135
135
|
|
|
136
136
|
`workflow-state.yaml` 当前 schema 为 2。修改状态实现时必须分别验证 `development_state` 与 `release_state` 的 scope、局部 revision、节点集合、Gate 集合和节点尝试。流程图不能退化为简单“上一编号节点”判断:平台准备允许并行,体验与产品允许带原因重开,后续迭代允许用成功的冻结基线物料满足未受影响的前置条件。
|
|
137
137
|
|
|
138
|
-
独立 aggregate 的 writer 仍通过共享提交区串行更新 JSONL 和全局视图,但使用各自的 aggregate revision:最多等待可识别的活动事务 30 秒,获取锁后重新读取并可分别成功。同一 aggregate 的 writer 在等待后必须因 revision conflict fail-closed
|
|
138
|
+
独立 aggregate 的 writer 仍通过共享提交区串行更新 JSONL 和全局视图,但使用各自的 aggregate revision:最多等待可识别的活动事务 30 秒,获取锁后重新读取并可分别成功。同一 aggregate 的 writer 在等待后必须因 revision conflict fail-closed。只读入口仅等待可证明仍存活的活动 writer;未知格式锁、失联 writer 和 journal 均不等待、不恢复,立即 fail-closed。
|
|
139
139
|
|
|
140
140
|
安装诊断不得从项目内可编辑 manifest 推导可信 Skill 集合。固定 Skill 清单、Skill 目录摘要、Hook 摘要和状态运行时摘要以发行包为准;manifest 必须与发行包一致。`doctor` 还必须运行安装后状态 CLI 的 `inspect` 与 `verify`,并校验 JSON 输出语义。
|
|
141
141
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 可重建视图
|
|
6
6
|
|
|
7
|
-
正式状态 mutation 会生成 `.workflow/control/events/<stream>/<revision>-<event-id>.yaml` 权威事件分片,并与 `workflow-state.yaml` 或 `artifact-registry.yaml` 当前视图及 `state-events.jsonl` 索引作为一组提交。workflow 与 artifact mutation 都使用 schema 2 增量事务:事件只保存顶层元数据和受影响的 scope 或物料,不再嵌入完整视图;scope start 同时固化项目身份。带已登记 evidence 的 transition/Gate 会在同一事务中同时生成 workflow delta 和 artifact handoff delta,并向 registry 顶层 `handoffs` 追加独立关系;已登记 artifact revision 的字段保持不变。提交前会在 `.workflow/delivery/runtime/formal-state-locks/` 写入恢复 journal;`inspect`、`verify`、Bootstrap 诊断及 Workflow Manager 正式状态读取期间持有同一把 formal-state lock
|
|
7
|
+
正式状态 mutation 会生成 `.workflow/control/events/<stream>/<revision>-<event-id>.yaml` 权威事件分片,并与 `workflow-state.yaml` 或 `artifact-registry.yaml` 当前视图及 `state-events.jsonl` 索引作为一组提交。workflow 与 artifact mutation 都使用 schema 2 增量事务:事件只保存顶层元数据和受影响的 scope 或物料,不再嵌入完整视图;scope start 同时固化项目身份。带已登记 evidence 的 transition/Gate 会在同一事务中同时生成 workflow delta 和 artifact handoff delta,并向 registry 顶层 `handoffs` 追加独立关系;已登记 artifact revision 的字段保持不变。提交前会在 `.workflow/delivery/runtime/formal-state-locks/` 写入恢复 journal;`inspect`、`verify`、Bootstrap 诊断及 Workflow Manager 正式状态读取期间持有同一把 formal-state lock,仅对可解析且确认 writer `pid` 仍存活的活动 mutation 做有界等待,不会返回混合 revision。未知格式锁、失联 writer、其他 reader 或未恢复 journal 均立即 fail-closed。mutation writer 最多等待可识别的活动提交 30 秒,获取锁后重新读取并校验自己的 aggregate revision。下一次 mutation 或 `rebuild` 根据 commit marker 回滚 prepared 事务或清理 committed 事务。视图丢失或损坏时执行:
|
|
8
8
|
|
|
9
9
|
```powershell
|
|
10
10
|
node .workflow/tools/state/state.mjs rebuild
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# 工作流与工作流管理器阶段现状
|
|
2
2
|
|
|
3
|
-
更新时间:2026-08-
|
|
3
|
+
更新时间:2026-08-23
|
|
4
4
|
|
|
5
5
|
本文是继续设计、实现或审核 Workflow Manager 时的阶段入口。它区分已经进入正式工作流源码的能力、已经接受但尚未实现的产品决策、仅在隔离原型中验证的交互,以及仍未完成的事项。领域术语以根目录 `CONTEXT.md` 为准,不以本文替代 ADR、正式状态契约或 Issue。
|
|
6
6
|
|
|
7
7
|
## 1. 当前基线
|
|
8
8
|
|
|
9
9
|
- 当前仓库是 `ai-delivery-workflow` 的维护与发行源码仓库,不是安装了工作流的业务项目。
|
|
10
|
-
-
|
|
10
|
+
- 当前维护基线的包版本为 `0.7.1`,本轮维护提交按 PATCH 版本完成发行闭环;对应发行提交、Git tag `v0.7.1` 和 npm 包身份由发布预检与发布后验证固定。`0.4.0`、`0.5.0`、`0.5.1`、`0.6.0` 和 `0.7.0` 的提交、Git tag、npm 包身份及校验和/生命周期证据均属于历史记录,仍保存在 `audit/` 中且不可改写;候选包和展开验证工作区保留在被忽略的 `.tmp/`。
|
|
11
11
|
- 正式维护源码位于 `bin/`、`lib/`、`skills/`、`docs/`、`verification/` 和 `audit/`。
|
|
12
12
|
- Workflow Manager 设计原型位于 `prototypes/workflow-manager/`,业务项目原型夹具位于 `prototypes/order-ops-business/`。两者是可版本化设计资产,不进入发行包,也不等于正式前端已经实现;运行时依赖、构建产物和一次性验证工作区仍放在 `.tmp/`。
|
|
13
13
|
|
|
@@ -156,17 +156,17 @@ Workflow Manager 从一开始就是人和 AI 共同使用的控制面。UI 与 W
|
|
|
156
156
|
|
|
157
157
|
当前正式浏览器测试遍历全部 19 个 Page,验证 P12 筛选、P13 Scene/Ctrl+V/保存、P14/P15 深链、P14 -> P13 Source Inspection Snapshot、P08 双 Tab/计划节点、P02 列头以及 `1280x720`、`1440x900` 无横向溢出或关键面板重叠;应用控制台无 error/warn。Manager 定向套件 97/97 通过,最终 `index.html` 引用的哈希资源已登记到 Git 索引。
|
|
158
158
|
|
|
159
|
-
### 4.1 `0.4.0` 候选验证
|
|
159
|
+
### 4.1 历史 `0.4.0` 候选验证
|
|
160
160
|
|
|
161
161
|
- 当前 P0/P1/P2 候选包的精确路径、SHA-256、文件数和生命周期报告由维护仓库的 `audit/WORKFLOW-MANAGER-P0-P1-P2-CLOSURE.md` 固定登记。审计记录不在 npm 包范围内,因此不会导致候选包自引用;实际 tarball 和展开验证工作区仍保留在 `.tmp/`。
|
|
162
162
|
- 隔离新安装的 Doctor 除可选 CodeGraph 提示外全部通过;安装后 Manager 的主页、健康检查、`workflow-manager.v1` Schema、19 个 Page、七节点投影、25 个可发现受控动作和 r33 Prototype 投影均已实测。
|
|
163
163
|
|
|
164
|
-
### 4.2 v2 Prototype catalog Candidate Assurance
|
|
164
|
+
### 4.2 历史 v2 Prototype catalog Candidate Assurance
|
|
165
165
|
|
|
166
166
|
- 新安装交付把 v2 Prototype Revision 的运行时、Manager、Agent Interface、Experience Skill 和全部必需 catalog 模板列为包表面。`doctor` 在包表面缺失时立即失败,不再继续计算会因缺失运行时而崩溃的受管资产摘要。
|
|
167
167
|
- 新的生产路径只接受 `prototype-revision-catalog.v2`。Experience Skill、Project Template、安装后的 Manager 与 Candidate Assurance 都从同一 Revision/checksum 读取事实;缺少任一必需 catalog 时由 CLI、HTTP 和 UI 确定性拒绝。
|
|
168
168
|
- `verification/workflow-manager.test.mjs` 的隔离 Candidate Assurance 用当前工作树实际执行 `npm pack`,在 `.tmp/` 的独立 consumer 安装 tarball,并由安装后的 `ai-delivery init` 与 `doctor` 建立项目。它用多 Logical Terminal 的 v2 fixture 证明安装后的 Manager CLI、HTTP 和 P07/P11-P17 浏览器 UI 读取同一 Prototype Revision 身份和 checksum:每个 Web UI 请求和返回都显式为 `TERM-WEB`,P12 的 `TERM-ADMIN` 路由实际显示 Admin 目标而不回退到 Web。Component Candidate 通过 P11 提交 `candidate -> under-review`、已安装 CLI 提交 `under-review -> registered`、HTTP 提交 `registered -> modified`;每次 apply 均产生 Receipt/Event,CLI 与 HTTP 均重放同一幂等键。初始 CLI/HTTP 计划仍返回相同 plan/successor;删除 v2 `components` catalog 后,三种入口返回相同受控拒绝。测试覆盖 `1280x720` 与 `1440x900` 桌面视口,且有效 Revision 路径没有浏览器 console error/warn。
|
|
169
|
-
- 可重建证据和边界记录在 `audit/WORKFLOW-MANAGER-V2-CANDIDATE-ASSURANCE.md`。它不替代 `audit/WORKFLOW-MANAGER-P0-P1-P2-CLOSURE.md`
|
|
169
|
+
- 可重建证据和边界记录在 `audit/WORKFLOW-MANAGER-V2-CANDIDATE-ASSURANCE.md`。它不替代 `audit/WORKFLOW-MANAGER-P0-P1-P2-CLOSURE.md` 的历史候选身份;其中记录的 `0.5.0` 发布闭环属于历史证据,不代表当前 HEAD 已执行发布。生成的 tarball、consumer、缓存和浏览器临时文件都留在被忽略的 `.tmp/` 并在测试结束后删除。
|
|
170
170
|
|
|
171
171
|
## 5. 开放事项
|
|
172
172
|
|
|
@@ -189,7 +189,7 @@ Workflow Manager 从一开始就是人和 AI 共同使用的控制面。UI 与 W
|
|
|
189
189
|
|
|
190
190
|
1. P0/P1/P2 及 Prototype 治理 01–07 已完成;实现提交为 `ae70304`,后续 v2 Candidate Assurance 收尾提交为 `db8abfb`,候选验证与 11 个 Gitee Issue 收尾均已完成。
|
|
191
191
|
2. 后续变更继续沿当前 `.workflow/`、schema 2 与 v2 Prototype Revision 契约推进。
|
|
192
|
-
3. r33
|
|
192
|
+
3. r33 晋级改变了当时的源码候选内容,历史 `0.4.0` r8 候选证据仍不可变且不能复用;该变更当时已在 `0.5.0` 中完成发布闭环。本轮 `0.7.1` 维护提交已通过 `release:check`、账号/2FA 和发布后验证;后续发布仍需从干净且同步的 `main` 重新生成候选。
|
|
193
193
|
|
|
194
194
|
## 6. 继续工作时必须读取的来源
|
|
195
195
|
|
package/lib/delivery-state.mjs
CHANGED
|
@@ -330,6 +330,7 @@ function pauseForLockRetry() {
|
|
|
330
330
|
function withLock(files, stream, action, lockOptions = {}) {
|
|
331
331
|
const recover = lockOptions.recover !== false;
|
|
332
332
|
const waitForActive = lockOptions.waitForActive === true;
|
|
333
|
+
let waitNotified = false;
|
|
333
334
|
const maximumAttempts = waitForActive ? Number.POSITIVE_INFINITY : 3;
|
|
334
335
|
const waitDeadline = waitForActive
|
|
335
336
|
? Date.now() + (lockOptions.waitTimeoutMs ?? ACTIVE_LOCK_WAIT_TIMEOUT_MS)
|
|
@@ -351,10 +352,20 @@ function withLock(files, stream, action, lockOptions = {}) {
|
|
|
351
352
|
} catch (error) {
|
|
352
353
|
if (error.code !== "EEXIST") throw error;
|
|
353
354
|
if (recover && removeAbandonedLock(lockFile)) continue;
|
|
354
|
-
if (waitForActive
|
|
355
|
+
if (waitForActive
|
|
356
|
+
&& isWaitableFormalLock(lockFile, { allowInitializing: stream !== "formal-state-read" })
|
|
357
|
+
&& Date.now() < waitDeadline) {
|
|
358
|
+
if (!waitNotified && typeof lockOptions.onWait === "function") {
|
|
359
|
+
waitNotified = true;
|
|
360
|
+
lockOptions.onWait({ lockFile, stream });
|
|
361
|
+
}
|
|
355
362
|
pauseForLockRetry();
|
|
356
363
|
continue;
|
|
357
364
|
}
|
|
365
|
+
// A live writer can release the lock between the failed open and metadata
|
|
366
|
+
// inspection. Retry that narrow race; an existing malformed/orphaned lock
|
|
367
|
+
// still falls through to the fail-closed error below.
|
|
368
|
+
if (waitForActive && !fs.existsSync(lockFile) && Date.now() < waitDeadline) continue;
|
|
358
369
|
throw new Error(`Formal state is locked while updating: ${stream}`);
|
|
359
370
|
}
|
|
360
371
|
}
|
|
@@ -371,19 +382,28 @@ function withLock(files, stream, action, lockOptions = {}) {
|
|
|
371
382
|
}
|
|
372
383
|
}
|
|
373
384
|
|
|
374
|
-
function isWaitableFormalLock(lockFile) {
|
|
385
|
+
function isWaitableFormalLock(lockFile, { allowInitializing = false } = {}) {
|
|
375
386
|
try {
|
|
376
387
|
const content = fs.readFileSync(lockFile, "utf8");
|
|
377
388
|
try {
|
|
378
389
|
const owner = JSON.parse(content);
|
|
379
|
-
|
|
390
|
+
if (typeof owner.stream !== "string" || owner.stream.length === 0
|
|
391
|
+
|| owner.stream === "formal-state-read"
|
|
392
|
+
|| !Number.isInteger(owner.pid) || owner.pid <= 0) return false;
|
|
393
|
+
try {
|
|
394
|
+
process.kill(owner.pid, 0);
|
|
395
|
+
return true;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return error.code === "EPERM";
|
|
398
|
+
}
|
|
380
399
|
} catch {
|
|
400
|
+
if (!allowInitializing) return false;
|
|
381
401
|
const ageMs = Date.now() - fs.statSync(lockFile).mtimeMs;
|
|
382
402
|
return ageMs >= 0 && ageMs < LOCK_METADATA_INITIALIZATION_TIMEOUT_MS;
|
|
383
403
|
}
|
|
384
404
|
} catch (error) {
|
|
385
405
|
// The owner may release the lock between our failed open and metadata read.
|
|
386
|
-
return
|
|
406
|
+
return false;
|
|
387
407
|
}
|
|
388
408
|
}
|
|
389
409
|
|
|
@@ -935,8 +955,12 @@ function assertNoPendingTransaction(files) {
|
|
|
935
955
|
}
|
|
936
956
|
}
|
|
937
957
|
|
|
938
|
-
function withReadableFormalState(files, action) {
|
|
939
|
-
return withLock(files, "formal-state-read", action, {
|
|
958
|
+
function withReadableFormalState(files, action, lockOptions = {}) {
|
|
959
|
+
return withLock(files, "formal-state-read", action, {
|
|
960
|
+
recover: false,
|
|
961
|
+
waitForActive: true,
|
|
962
|
+
onWait: lockOptions.onWait,
|
|
963
|
+
});
|
|
940
964
|
}
|
|
941
965
|
|
|
942
966
|
function withZeroWriteFormalState(files, action) {
|
|
@@ -2533,7 +2557,10 @@ function transitionWorkflow(files, flags, options) {
|
|
|
2533
2557
|
});
|
|
2534
2558
|
if (completionReceipt) result.completion_receipt = completionReceipt;
|
|
2535
2559
|
return result;
|
|
2536
|
-
}, {
|
|
2560
|
+
}, {
|
|
2561
|
+
waitForActive: flags.has("--expected-scope-revision"),
|
|
2562
|
+
onWait: options.onLockWait,
|
|
2563
|
+
});
|
|
2537
2564
|
}
|
|
2538
2565
|
|
|
2539
2566
|
function registeredEvidence(files, evidence, options, node = null) {
|
|
@@ -3208,6 +3235,7 @@ export function executeDeliveryStateCommand(argv, options = {}) {
|
|
|
3208
3235
|
history: flags.has("--history"),
|
|
3209
3236
|
sinceAnchor: flags.has("--since-anchor"),
|
|
3210
3237
|
}),
|
|
3238
|
+
{ onWait: options.onLockWait },
|
|
3211
3239
|
);
|
|
3212
3240
|
} else if (command === "snapshot" && positional[1] === "create") {
|
|
3213
3241
|
result = createFormalSnapshot(files, flags, options);
|
|
@@ -3234,7 +3262,7 @@ export function executeDeliveryStateCommand(argv, options = {}) {
|
|
|
3234
3262
|
scope_manifest: currentScopeManifest(workflowState.development_state),
|
|
3235
3263
|
derived_node_states: deriveNodeStates(files, workflowState, artifactRegistry, options),
|
|
3236
3264
|
};
|
|
3237
|
-
});
|
|
3265
|
+
}, { onWait: options.onLockWait });
|
|
3238
3266
|
} else {
|
|
3239
3267
|
throw new Error(
|
|
3240
3268
|
"Use inspect, verify, rebuild, snapshot create, artifact register/prune, scope freeze/amend/warn, "
|
|
@@ -243,8 +243,13 @@ function managedFilesEqual(left, right) {
|
|
|
243
243
|
|
|
244
244
|
function directoryDigest(root) {
|
|
245
245
|
const hash = crypto.createHash("sha256");
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
const entries = filesUnder(root)
|
|
247
|
+
.map((file) => ({
|
|
248
|
+
file,
|
|
249
|
+
relative: path.relative(root, file).replaceAll("\\", "/"),
|
|
250
|
+
}))
|
|
251
|
+
.sort((left, right) => left.relative.localeCompare(right.relative));
|
|
252
|
+
for (const { file, relative } of entries) {
|
|
248
253
|
hash.update(relative);
|
|
249
254
|
hash.update("\0");
|
|
250
255
|
hash.update(normalizedManagedContent(file));
|
|
@@ -292,8 +297,14 @@ function exactPathDigest(target) {
|
|
|
292
297
|
|
|
293
298
|
function sourceSetDigest(sources) {
|
|
294
299
|
const hash = crypto.createHash("sha256");
|
|
295
|
-
|
|
296
|
-
|
|
300
|
+
const entries = [...sources]
|
|
301
|
+
.map((item) => ({
|
|
302
|
+
item,
|
|
303
|
+
relative: item.relative.replaceAll("\\", "/"),
|
|
304
|
+
}))
|
|
305
|
+
.sort((left, right) => left.relative.localeCompare(right.relative));
|
|
306
|
+
for (const { item, relative } of entries) {
|
|
307
|
+
hash.update(relative);
|
|
297
308
|
hash.update("\0");
|
|
298
309
|
hash.update(normalizedManagedContent(item.source));
|
|
299
310
|
hash.update("\0");
|
package/lib/workspace.mjs
CHANGED
|
@@ -113,7 +113,10 @@ function initializeWorkflowRepository(workspaceRoot, branch, remote, heads, acti
|
|
|
113
113
|
const current = gitBranch(workspaceRoot);
|
|
114
114
|
if (current !== branch) {
|
|
115
115
|
if (gitHasCommit(workspaceRoot)) {
|
|
116
|
-
throw new Error(
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Existing workflow repository must already use ${branch}; automatic branch changes are not supported. `
|
|
118
|
+
+ `Create or switch to the required branch with 'git switch -c ${branch}' and rerun init.`,
|
|
119
|
+
);
|
|
117
120
|
}
|
|
118
121
|
if (!dryRun) runGit(workspaceRoot, ["symbolic-ref", "HEAD", `refs/heads/${branch}`]);
|
|
119
122
|
}
|