dsh-punky-swarm 0.3.6
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/CHANGELOG.md +120 -0
- package/LICENSE +661 -0
- package/README.en.md +281 -0
- package/README.md +281 -0
- package/cordis.patch.yml +59 -0
- package/lib/acps/certs.js +310 -0
- package/lib/acps/discovery-client.js +283 -0
- package/lib/acps/registry-client.js +527 -0
- package/lib/acps/server.js +302 -0
- package/lib/aip/agent-descriptor.js +170 -0
- package/lib/aip/identity.js +469 -0
- package/lib/aip/tool-descriptor.js +93 -0
- package/lib/api.js +266 -0
- package/lib/artifact-types.js +48 -0
- package/lib/assembly/audit-blind-review.js +170 -0
- package/lib/assembly/schema.js +253 -0
- package/lib/assembly.js +41 -0
- package/lib/assets.js +103 -0
- package/lib/bridge/trajectory.js +202 -0
- package/lib/client.js +704 -0
- package/lib/comms/acps-bridge.js +219 -0
- package/lib/comms/aip-format.js +141 -0
- package/lib/comms/budget.js +124 -0
- package/lib/comms/mailbox.js +221 -0
- package/lib/comms/topic-runtime.js +76 -0
- package/lib/comms/topic.js +115 -0
- package/lib/discovery/filter.js +199 -0
- package/lib/discovery/schema.js +157 -0
- package/lib/discovery/service.js +251 -0
- package/lib/hot/config-watch.js +252 -0
- package/lib/index.js +433 -0
- package/lib/lock.js +69 -0
- package/lib/panel/batch-detail.js +163 -0
- package/lib/panel/batch-list.js +84 -0
- package/lib/panel/locales.js +93 -0
- package/lib/panel/main.js +270 -0
- package/lib/panel/stream.js +348 -0
- package/lib/panel/theme.js +103 -0
- package/lib/panel/widgets.js +63 -0
- package/lib/schema.d.ts +192 -0
- package/lib/schema.js +259 -0
- package/lib/schema.ts +398 -0
- package/lib/state/archive.js +174 -0
- package/lib/state/command-exec.js +148 -0
- package/lib/state/constants.js +37 -0
- package/lib/state/corrupt-registry.js +103 -0
- package/lib/state/event-types.js +65 -0
- package/lib/state/gates.d.ts +226 -0
- package/lib/state/gates.js +428 -0
- package/lib/state/gates.ts +348 -0
- package/lib/state/machine-rules.d.ts +14 -0
- package/lib/state/machine-rules.js +76 -0
- package/lib/state/machine-rules.ts +88 -0
- package/lib/state/machine.js +68 -0
- package/lib/state/resume.js +201 -0
- package/lib/state/schema-v3.d.ts +10 -0
- package/lib/state/schema-v3.js +57 -0
- package/lib/state/schema-v3.ts +80 -0
- package/lib/state/store.js +607 -0
- package/lib/state/task-utils.js +40 -0
- package/lib/tools/core.js +323 -0
- package/lib/tools/git-utils.js +44 -0
- package/lib/tools/lane-tools.js +455 -0
- package/lib/tools/log-tools.js +173 -0
- package/lib/tools/mailbox-tools.js +121 -0
- package/lib/tools/merge-agent.js +127 -0
- package/lib/tools/register.js +85 -0
- package/lib/tools/shared.js +32 -0
- package/lib/types/contracts.d.ts +277 -0
- package/lib/types/contracts.js +17 -0
- package/lib/types/contracts.ts +279 -0
- package/lib/verify/evidence.js +249 -0
- package/lib/verify/gate.js +161 -0
- package/lib/verify/mount.js +48 -0
- package/lib/verify/selector.js +101 -0
- package/lib/watch/lane-heartbeat.js +333 -0
- package/lib/wave-plan.d.ts +48 -0
- package/lib/wave-plan.js +473 -0
- package/lib/wave-plan.ts +465 -0
- package/package.json +71 -0
- package/presets/jiufeng/NOTICE +5 -0
- package/presets/jiufeng/agent.cordis.yml +275 -0
- package/presets/jiufeng/preset.yml +3 -0
- package/skills/jiufeng-team/SKILL.md +214 -0
- package/skills/jiufeng-team/references/constitution.md +62 -0
- package/skills/jiufeng-team/references/roles/coder.md +17 -0
- package/skills/jiufeng-team/references/roles/coordinator.md +13 -0
- package/skills/jiufeng-team/references/roles/designer.md +14 -0
- package/skills/jiufeng-team/references/roles/doc-manager.md +13 -0
- package/skills/jiufeng-team/references/roles/manager.md +22 -0
- package/skills/jiufeng-team/references/roles/reviewer.md +19 -0
- package/skills/jiufeng-team/references/roles/supervisor.md +13 -0
- package/skills/jiufeng-team/references/roles/tester.md +13 -0
- package/skills/jiufeng-team/references/templates/call-chain-matrix-template.md +121 -0
- package/skills/jiufeng-team/references/templates/endpoint-behavior-template.md +60 -0
- package/skills/jiufeng-team/references/templates/gap-list.json +33 -0
- package/skills/jiufeng-team/references/templates/leader-decision-pack.md +92 -0
- package/skills/jiufeng-team/references/templates/plan-template.md +61 -0
- package/skills/jiufeng-team/references/templates/success-pattern-seeds.md +126 -0
- package/skills/jiufeng-team/references/workflow.md +98 -0
- package/skills/jiufeng-team/scripts/check-v3-density.mjs +98 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2025-2026 Punky
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as
|
|
6
|
+
published by the Free Software Foundation, either version 3 of the
|
|
7
|
+
License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// lib/state/resume.js — 断点续跑骨架(增强恢复落地后回填实现)
|
|
19
|
+
//
|
|
20
|
+
// 职责(断点续跑:执行模型改变——与增强恢复的"执行模型不变"互补):
|
|
21
|
+
// 状态重建接口 —— 恢复 running/review(而非一律 idle)+ config.resume.enabled 开关(默认关)
|
|
22
|
+
// 断点指针语义 —— laneProgress 步骤进度(schema-v3.js 可选字段;本文件提供读写/清退接口骨架)
|
|
23
|
+
// worker 任务包契约断点续跑 —— resume 章节占位(增强恢复落地后装配层实际注入)
|
|
24
|
+
//
|
|
25
|
+
// 纪律:
|
|
26
|
+
// - 默认关:config.resume.enabled !== true → recoverBatches() 原样委托 store.recoverBatches()(零行为变化)
|
|
27
|
+
// - 不新增成员态:恢复对象仅 running/review(crash 中断的 in-flight lane);failed/conflict/skipped
|
|
28
|
+
// 不参与任何恢复,失败是裁决结果不是中断,不复活
|
|
29
|
+
// - 恢复不走 setMember:直接保留状态 + 事件留痕,不重验 entry gate/condition(恢复不是新派发)
|
|
30
|
+
// - 不提供批次内自动续跑:重做仍开新批次
|
|
31
|
+
//
|
|
32
|
+
// 边界(P1-02 接线后同步):本文件只读 store 公共面(listSessions/listBatches/readBatch/appendEvent),
|
|
33
|
+
// 不触碰 store.js recoverBatches 主体(归 recover-audit lane)与 lane-tools.js 契约边界。
|
|
34
|
+
// P1-02 已接线:index.js 启动恢复改调本文件 recoverBatches(restoreRunning 按 config.resume.enabled);
|
|
35
|
+
// laneProgress 写点经 lane-tools.js lane_checkpoint(progress 携带时调 store.updateLaneProgress,
|
|
36
|
+
// 内部 laneProgressWrite 纯函数合并)+ 结算终态清退经 store.setMember(laneProgressClear)。
|
|
37
|
+
import * as schema from '../schema.js';
|
|
38
|
+
// P1-04 单点:findTask 收敛至 task-utils.js(overBudgetOf 原 :157-162 内联同构遍历删除)
|
|
39
|
+
import { findTask } from './task-utils.js';
|
|
40
|
+
// R-01 发端/读端收敛:事件字面量改引 EVT 常量单点(resume.js:99 双写法 + :179 读端)
|
|
41
|
+
import * as EVT from './event-types.js';
|
|
42
|
+
|
|
43
|
+
// ── config.resume 开关(默认关,零运行时开销,行为不变)──
|
|
44
|
+
export const RESUME_DEFAULTS = Object.freeze({ enabled: false });
|
|
45
|
+
|
|
46
|
+
export function resolveResumeConfig(config) {
|
|
47
|
+
const c = config?.resume ?? {};
|
|
48
|
+
// resume 键不在 CAPABILITY_REGISTRY(config.resume.* 顶层域,非 capabilities.*)——
|
|
49
|
+
// 缺省关语义即严格判等(RESUME_DEFAULTS.enabled=false),无需 readCapability(无 entry 返回 undefined)
|
|
50
|
+
return { enabled: c.enabled === true };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 待回填清单(P1-02 接线后:1/2 已回填;剩余项为后续批次):
|
|
54
|
+
export const RESUME_FILL_POINTS = [
|
|
55
|
+
// ✓ 已接线(P1-02,本批):恢复接口实现 + config.resume 接线(index.js 启动恢复改调 resume.recoverBatches(store, { restoreRunning: resumeCfg.enabled }))
|
|
56
|
+
// ✓ 已接线(P1-02,本批):laneProgress 字段写/清(lane_checkpoint 携带 progress 时经 laneProgressWrite 写入;
|
|
57
|
+
// lane 结算终态经 laneProgressClear 清退)
|
|
58
|
+
// ✗ 待回填:batch_status 面板 progress 视图(操作面板批次)
|
|
59
|
+
'任务包 resume 章节从"文档占位"升为"装配层实际注入"(worker 角色手册加 resume 条款)',
|
|
60
|
+
'崩溃恢复用例测试(模拟 running 中 crash → 重启 restore → 新 worker 从 N+1 续跑 → 产物合并验证)',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// ── 状态重建接口 ──
|
|
64
|
+
// 单一入口扩展:签名与 store.recoverBatches 同构,加可选 restoreRunning。
|
|
65
|
+
// restoreRunning 缺省 false → 原样委托 store.recoverBatches()(默认路径零改动;detail 增强经 store 生效,不受本模块影响)
|
|
66
|
+
// restoreRunning === true → restoreBatches()(running→running / review→review 原地保留)
|
|
67
|
+
// 幂等与安全:同一恢复调用内每 lane 二选一(restore 与 recover 互斥,由本布尔分支保证)。
|
|
68
|
+
// 接线点:index.js 启动恢复处 `resume.recoverBatches(store, { restoreRunning: resumeCfg.enabled })`,
|
|
69
|
+
// 返回数组形态与 store.recoverBatches 一致(.length 兼容既有 `if (r.length)` 日志)。
|
|
70
|
+
export function recoverBatches(store, { restoreRunning = false } = {}) {
|
|
71
|
+
if (restoreRunning !== true) return store.recoverBatches();
|
|
72
|
+
return restoreBatches(store);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 独立恢复入口(语义更清晰):只处理非终态批次的 running/review lane——
|
|
76
|
+
// 原地保留状态(不落 setMember,不重验 entry gate/condition)+ 事件留痕,返回 'sessionId/batchId' 数组。
|
|
77
|
+
// 骨架已实现:状态保留 + system.recovered{detail[].restored=true} / system.restored 事件留痕。
|
|
78
|
+
// (增强恢复落地后回填,均只读探测,不改产物):produced 证据(gateStatus outputsMissing/produceMissing 反推)、
|
|
79
|
+
// lastActiveAt(batch.events 该 lane 最近事件 ts,回退 updatedAt)、progress(laneProgress[lane] 合并)。
|
|
80
|
+
export function restoreBatches(store, { eventType = EVT.EVT_SYSTEM_RESTORED } = {}) {
|
|
81
|
+
const restored = [];
|
|
82
|
+
for (const sessionId of store.listSessions()) {
|
|
83
|
+
for (const batchId of store.listBatches(sessionId)) {
|
|
84
|
+
const batch = store.readBatch(sessionId, batchId);
|
|
85
|
+
if (!batch || schema.isBatchTerminal(batch.phase)) continue; // 终态批次跳过
|
|
86
|
+
const detail = [];
|
|
87
|
+
for (const [lane, state] of Object.entries(batch.lanes ?? {})) {
|
|
88
|
+
if (state !== 'running' && state !== 'review') continue; // 仅 crash 中断的 in-flight lane
|
|
89
|
+
detail.push({
|
|
90
|
+
lane,
|
|
91
|
+
from: state,
|
|
92
|
+
to: state, // 原地保留(恢复 running/review 而非一律 idle)
|
|
93
|
+
restored: true, // 恢复标记(与 gate_status 面板展示的衔接)
|
|
94
|
+
// produced(已产出产物清单,复用 gate 语义只读探测)、
|
|
95
|
+
// lastActiveAt(上次活动时间,batch.events 该 lane 最近事件 ts → 回退 batch.updatedAt)、
|
|
96
|
+
// progress(laneProgress[lane] 断点指针)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (detail.length) {
|
|
100
|
+
// 事件留痕:appendEvent 只追加事件 + updatedAt,不改 lanes——恢复路径不落 setMember(gate 交互设计)
|
|
101
|
+
// R-01 双写法收敛:system.recovered(recover 语义)+ eventType 缺省 system.restored(restore 语义)均引 EVT 常量
|
|
102
|
+
store.appendEvent(sessionId, batchId, EVT.EVT_SYSTEM_RECOVERED, {
|
|
103
|
+
batchId, sessionId,
|
|
104
|
+
recoveredLanes: detail.map((d) => d.lane), // 保留既有字段(batch-store.test.js 断言其存在,向后兼容)
|
|
105
|
+
detail,
|
|
106
|
+
restored: true,
|
|
107
|
+
});
|
|
108
|
+
store.appendEvent(sessionId, batchId, eventType, {
|
|
109
|
+
batchId, sessionId,
|
|
110
|
+
restoredLanes: detail.map((d) => d.lane),
|
|
111
|
+
detail,
|
|
112
|
+
});
|
|
113
|
+
restored.push(sessionId + '/' + batchId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return restored; // 与 recoverBatches 同形态(数组,.length 兼容 index.js 日志)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── 断点指针语义(laneProgress,schema-v3.js 可选字段)──
|
|
121
|
+
// 纯函数:操作 batch 对象,不落盘(写入/清退由 B 的 lane_checkpoint / 结算路径调用本接口后交 store 原子写)
|
|
122
|
+
|
|
123
|
+
// 读:无进度记录 → null(缺省 undefined 语义,读兼容)
|
|
124
|
+
export function laneProgressRead(batch, lane) {
|
|
125
|
+
return batch?.laneProgress?.[lane] ?? null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 值级校验:status ∈ running|review(与成员态对齐,不新增成员态 S2);step/total 正整数且 step ≤ total
|
|
129
|
+
export function isValidLaneProgress(p) {
|
|
130
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) return false;
|
|
131
|
+
if (p.status !== 'running' && p.status !== 'review') return false;
|
|
132
|
+
if (!Number.isInteger(p.step) || !Number.isInteger(p.total)) return false;
|
|
133
|
+
if (p.step < 1 || p.total < 1 || p.step > p.total) return false;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 写:返回新 batch(不突变入参)。lane_checkpoint 携带 progress 时调用,
|
|
138
|
+
// 更新 laneProgress[lane](断点指针:lane 执行到第 N 步,新 worker 从 N+1 继续),随后交 store 原子写。
|
|
139
|
+
export function laneProgressWrite(batch, lane, progress) {
|
|
140
|
+
if (!isValidLaneProgress(progress)) {
|
|
141
|
+
throw new Error('invalid laneProgress: ' + JSON.stringify(progress) + ' (expect { step, total, status: running|review, updatedAt? })');
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
...batch,
|
|
145
|
+
laneProgress: {
|
|
146
|
+
...(batch.laneProgress ?? {}),
|
|
147
|
+
[lane]: { ...progress, updatedAt: progress.updatedAt ?? new Date().toISOString() },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 清:lane 结算(merged/failed/skipped/conflict)时删除该 lane 指针,不残留脏指针;
|
|
153
|
+
// 批次 complete 后整块随批次归档(archive 已覆盖)。返回新 batch;无指针则原样返回。
|
|
154
|
+
export function laneProgressClear(batch, lane) {
|
|
155
|
+
if (!batch?.laneProgress?.[lane]) return batch;
|
|
156
|
+
const lp = { ...batch.laneProgress };
|
|
157
|
+
delete lp[lane];
|
|
158
|
+
return { ...batch, laneProgress: Object.keys(lp).length ? lp : undefined };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── 步数预算(超限判定,纯函数;接线在 lane-tools.js lane_checkpoint,B 子项)──
|
|
162
|
+
// 设计:任务建批时声明 checkpoint:{steps}(总步数预算,wave-plan.js normalizeResumeContract
|
|
163
|
+
// 校验正整数或 null 后透传进 wavePlan);lane_checkpoint 携带 progress 时判定
|
|
164
|
+
// progress.total > checkpoint.steps → 超限。判定只发信号不硬杀:接线层命中且事件流无该 lane
|
|
165
|
+
// 的 lane.over-budget → appendEvent(幂等),转 review/stalled 由 Manager/Leader 裁决。
|
|
166
|
+
// 返回 { over, budget }:budget = 任务声明的步数上限(未声明 = null);over = total 是否超限。
|
|
167
|
+
// B-不变量:未声明 checkpoint.steps(budget=null)零感知——任意 progress 返回 { over:false, budget:null }。
|
|
168
|
+
// 任务定位语义与 store/machine/gates/lane-heartbeat 一致:wavePlan[].tasks 按 id 匹配
|
|
169
|
+
// (P1-04 单点:改调 task-utils.findTask,删除原 :157-162 内联同构遍历)。
|
|
170
|
+
export function overBudgetOf(batch, lane, progress) {
|
|
171
|
+
const task = findTask(batch, lane);
|
|
172
|
+
const budget = task?.checkpoint?.steps ?? null;
|
|
173
|
+
if (budget === null) return { over: false, budget: null }; // 未声明 → 零感知
|
|
174
|
+
const total = progress?.total;
|
|
175
|
+
if (Number.isInteger(total) && total > budget) return { over: true, budget };
|
|
176
|
+
return { over: false, budget };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// B-幂等判据:事件流已存在该 lane 的 lane.over-budget → true(接线层据此避免重复 appendEvent;
|
|
180
|
+
// 同 lane 超限事件只发一次,重复 progress 调用不重复发)
|
|
181
|
+
export function hasOverBudgetEvent(batch, lane) {
|
|
182
|
+
return (batch?.events ?? []).some((e) => e.type === EVT.EVT_LANE_OVER_BUDGET && e.lane === lane);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── worker 任务包契约断点续跑(骨架,增强恢复落地后填充)──
|
|
186
|
+
// 返回任务包 resume 章节占位:B 完成后由装配层(worker 角色手册)按
|
|
187
|
+
// config.resume.enabled && capabilities.worktree.enabled 置 enabled=true 并注入派发提示词。
|
|
188
|
+
// 四步契约语义:N = laneProgress.step(最近一次 checkpoint 的 progress.step),resumeFrom = N+1。
|
|
189
|
+
export function workerResumeChapter() {
|
|
190
|
+
return {
|
|
191
|
+
title: 'resume(断点续跑)',
|
|
192
|
+
enabled: false, // 增强恢复落地后装配层按开关置 true 并实际注入
|
|
193
|
+
steps: [
|
|
194
|
+
'查询 laneProgress(laneProgressRead)/ lane_checkpoint_status → 得 { resumeFrom: N+1, total, produced }',
|
|
195
|
+
'从第 N+1 步开始执行;已完成步骤(step ≤ N)的产物视为已交付,禁止重做',
|
|
196
|
+
'每步完成即 lane_checkpoint(progress={ step, total })(经 laneProgressWrite 写 laneProgress)',
|
|
197
|
+
'完成态(done)后正常 running→review→merged(exit gate 校验 outputs 齐备)',
|
|
198
|
+
],
|
|
199
|
+
fillPoints: RESUME_FILL_POINTS,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Batch, LaneProgressMap } from '../types/contracts.js';
|
|
2
|
+
export declare const BATCH_SCHEMA_V3 = 3;
|
|
3
|
+
export declare function chainsDefaults(): {
|
|
4
|
+
chains: {};
|
|
5
|
+
order: never[];
|
|
6
|
+
};
|
|
7
|
+
export declare function laneProgressDefaults(): undefined;
|
|
8
|
+
export declare function isLaneProgress(v: unknown): v is LaneProgressMap;
|
|
9
|
+
export declare function conditionDefaults(): null;
|
|
10
|
+
export declare function migrateV2toV3(batch: unknown): Batch;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2025-2026 Punky
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as
|
|
6
|
+
published by the Free Software Foundation, either version 3 of the
|
|
7
|
+
License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
export const BATCH_SCHEMA_V3 = 3;
|
|
18
|
+
export function chainsDefaults() {
|
|
19
|
+
return { chains: {}, order: [] };
|
|
20
|
+
}
|
|
21
|
+
// laneProgress 缺省(undefined = 无断点进度);对外显式化缺省语义,供消费方/测试引用
|
|
22
|
+
export function laneProgressDefaults() {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
// laneProgress 形态校验(批次级 plain object,值级校验由 resume.js isValidLaneProgress 承担)
|
|
26
|
+
export function isLaneProgress(v) {
|
|
27
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
28
|
+
}
|
|
29
|
+
// lane.condition 缺省(null = 恒满足);建批无 condition 声明时落此缺省
|
|
30
|
+
export function conditionDefaults() {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
export function migrateV2toV3(batch) {
|
|
34
|
+
if (!batch || typeof batch !== 'object' || Array.isArray(batch)) {
|
|
35
|
+
throw new Error('invalid batch: expected object');
|
|
36
|
+
}
|
|
37
|
+
const b = batch; // 单点断言:typeof/Array.isArray 守卫后为 plain object(字段操作面)
|
|
38
|
+
if (b.schema === BATCH_SCHEMA_V3)
|
|
39
|
+
return batch; // 幂等:已 v3 直接返回(不 clone,避免无谓分配)
|
|
40
|
+
const next = { ...b };
|
|
41
|
+
if (next.chains == null || typeof next.chains !== 'object' || Array.isArray(next.chains)) {
|
|
42
|
+
next.chains = chainsDefaults();
|
|
43
|
+
}
|
|
44
|
+
// archived 迁移兜底(与 chains 兜底并列;chains/condition 逻辑不动)
|
|
45
|
+
if (next.archived == null || typeof next.archived !== 'boolean') {
|
|
46
|
+
next.archived = false;
|
|
47
|
+
}
|
|
48
|
+
// laneProgress 迁移兜底(幂等)——非法形态(非 plain object)归一为缺省(undefined);
|
|
49
|
+
// 合法形态原样保留(含空对象 {} = 无进度记录);缺省 undefined 不写字段(零噪音,读兼容)
|
|
50
|
+
if (next.laneProgress != null && !isLaneProgress(next.laneProgress)) {
|
|
51
|
+
delete next.laneProgress;
|
|
52
|
+
}
|
|
53
|
+
next.schema = BATCH_SCHEMA_V3;
|
|
54
|
+
// 边界断言(经 unknown 桥):Record<string, unknown> 无 Batch 必填字段不充分重叠——迁移产物与 Batch 契约同源
|
|
55
|
+
// (store.createBatch 形态 + 兜底字段),断言纯类型层、运行期零变更
|
|
56
|
+
return next;
|
|
57
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2025-2026 Punky
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as
|
|
6
|
+
published by the Free Software Foundation, either version 3 of the
|
|
7
|
+
License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// SchemaV3:batch schema v3 —— 新增可选字段 chains(C4 mailbox 环防护记账状态,batch JSON 唯一事实源)
|
|
19
|
+
// + archived(done→archive:单向归档标记,布尔,缺省 false)
|
|
20
|
+
// chains shape(与 dsh-team TeamState.chains 同构):
|
|
21
|
+
// { chains: { [chainId]: { edges: { [from→to]: count }, said: { [from→to]: lastText } } }, order: [chainId] }
|
|
22
|
+
// 落点:batch.chains;v2→v3 迁移幂等(存量批次自动补 chains 默认 + archived:false,schema 升 3;已 v3 原样返回)
|
|
23
|
+
// lane 条件:condition 为 lane 级可选字段(wavePlan.wavePlan[].tasks[].condition,建批静态声明),
|
|
24
|
+
// 缺省 null = 恒满足(v2/v3 存量批次任务对象无该字段 → 读取天然兼容,不报错);
|
|
25
|
+
// 迁移兜底由消费方(machine.checkDispatchCondition 见 null 即恒满足)+ 建批规范化共同保证,
|
|
26
|
+
// migrateV2toV3 不遍历 wavePlan(chains 逻辑一字不动,避免触碰 batch 结构)。
|
|
27
|
+
// 断点指针:laneProgress 为批次级可选字段——
|
|
28
|
+
// { laneProgress: { [laneId]: { step: number, total: number, status: 'running'|'review', updatedAt: ISO } } }
|
|
29
|
+
// 缺省 undefined(无进度记录 = 未断点);status 与成员态对齐,不新增成员态;
|
|
30
|
+
// 沿用"可选字段 + migrate 幂等兜底"模式(不升大版本):非法形态(非 plain object)归一为缺省,
|
|
31
|
+
// 存量 v3 批次缺字段 = 缺省,读取天然兼容;写入点/清退点由 lib/state/resume.js(断点指针接口)与 lane_checkpoint 承担。
|
|
32
|
+
// Phase 2 类型化:migrateV2toV3 输入 unknown(任意 JSON 解析产物最诚实),内部既有 typeof/Array.isArray 守卫保留,
|
|
33
|
+
// 字段操作在 Record<string, unknown> 上进行,边界断言返回 Batch——运行期语义零变更(断言纯类型层)。
|
|
34
|
+
import type { Batch, LaneProgressMap } from '../types/contracts.js';
|
|
35
|
+
|
|
36
|
+
export const BATCH_SCHEMA_V3 = 3;
|
|
37
|
+
|
|
38
|
+
export function chainsDefaults() {
|
|
39
|
+
return { chains: {}, order: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// laneProgress 缺省(undefined = 无断点进度);对外显式化缺省语义,供消费方/测试引用
|
|
43
|
+
export function laneProgressDefaults() {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// laneProgress 形态校验(批次级 plain object,值级校验由 resume.js isValidLaneProgress 承担)
|
|
48
|
+
export function isLaneProgress(v: unknown): v is LaneProgressMap {
|
|
49
|
+
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// lane.condition 缺省(null = 恒满足);建批无 condition 声明时落此缺省
|
|
53
|
+
export function conditionDefaults() {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function migrateV2toV3(batch: unknown): Batch {
|
|
58
|
+
if (!batch || typeof batch !== 'object' || Array.isArray(batch)) {
|
|
59
|
+
throw new Error('invalid batch: expected object');
|
|
60
|
+
}
|
|
61
|
+
const b = batch as Record<string, unknown>; // 单点断言:typeof/Array.isArray 守卫后为 plain object(字段操作面)
|
|
62
|
+
if (b.schema === BATCH_SCHEMA_V3) return batch as Batch; // 幂等:已 v3 直接返回(不 clone,避免无谓分配)
|
|
63
|
+
const next = { ...b };
|
|
64
|
+
if (next.chains == null || typeof next.chains !== 'object' || Array.isArray(next.chains)) {
|
|
65
|
+
next.chains = chainsDefaults();
|
|
66
|
+
}
|
|
67
|
+
// archived 迁移兜底(与 chains 兜底并列;chains/condition 逻辑不动)
|
|
68
|
+
if (next.archived == null || typeof next.archived !== 'boolean') {
|
|
69
|
+
next.archived = false;
|
|
70
|
+
}
|
|
71
|
+
// laneProgress 迁移兜底(幂等)——非法形态(非 plain object)归一为缺省(undefined);
|
|
72
|
+
// 合法形态原样保留(含空对象 {} = 无进度记录);缺省 undefined 不写字段(零噪音,读兼容)
|
|
73
|
+
if (next.laneProgress != null && !isLaneProgress(next.laneProgress)) {
|
|
74
|
+
delete next.laneProgress;
|
|
75
|
+
}
|
|
76
|
+
next.schema = BATCH_SCHEMA_V3;
|
|
77
|
+
// 边界断言(经 unknown 桥):Record<string, unknown> 无 Batch 必填字段不充分重叠——迁移产物与 Batch 契约同源
|
|
78
|
+
// (store.createBatch 形态 + 兜底字段),断言纯类型层、运行期零变更
|
|
79
|
+
return next as unknown as Batch;
|
|
80
|
+
}
|