@accelerator-mzq/forge 3.0.0 → 4.0.0
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/README.md +12 -12
- package/dist/cli/commands/ack.d.ts.map +1 -1
- package/dist/cli/commands/ack.js +87 -12
- package/dist/cli/commands/ack.js.map +1 -1
- package/dist/cli/commands/archive.d.ts +32 -33
- package/dist/cli/commands/archive.d.ts.map +1 -1
- package/dist/cli/commands/archive.js +339 -667
- package/dist/cli/commands/archive.js.map +1 -1
- package/dist/cli/commands/legacy-bridge.js +1 -1
- package/dist/cli/commands/legacy-bridge.js.map +1 -1
- package/dist/cli/commands/upgrade.d.ts.map +1 -1
- package/dist/cli/commands/upgrade.js +2 -51
- package/dist/cli/commands/upgrade.js.map +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +4 -25
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/index.js +0 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/core/ack/marker-ack.d.ts +28 -0
- package/dist/core/ack/marker-ack.d.ts.map +1 -0
- package/dist/core/ack/marker-ack.js +155 -0
- package/dist/core/ack/marker-ack.js.map +1 -0
- package/dist/core/ack-log.d.ts.map +1 -1
- package/dist/core/ack-log.js +10 -7
- package/dist/core/ack-log.js.map +1 -1
- package/dist/core/archive/index.d.ts +2 -3
- package/dist/core/archive/index.d.ts.map +1 -1
- package/dist/core/archive/index.js +7 -5
- package/dist/core/archive/index.js.map +1 -1
- package/dist/core/archive/pause-decisions-fence.js +12 -12
- package/dist/core/archive/pause-decisions-fence.js.map +1 -1
- package/dist/core/archive/summary-builder.d.ts +26 -21
- package/dist/core/archive/summary-builder.d.ts.map +1 -1
- package/dist/core/archive/summary-builder.js +115 -223
- package/dist/core/archive/summary-builder.js.map +1 -1
- package/dist/core/archive/summary-render.d.ts +5 -3
- package/dist/core/archive/summary-render.d.ts.map +1 -1
- package/dist/core/archive/summary-render.js +38 -44
- package/dist/core/archive/summary-render.js.map +1 -1
- package/dist/core/lock.d.ts +46 -0
- package/dist/core/lock.d.ts.map +1 -0
- package/dist/core/lock.js +98 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/markers/types.d.ts +27 -134
- package/dist/core/markers/types.d.ts.map +1 -1
- package/dist/core/markers/types.js +10 -1
- package/dist/core/markers/types.js.map +1 -1
- package/dist/core/migrate/index.js +1 -1
- package/dist/core/migrate/index.js.map +1 -1
- package/dist/core/monitor/artifact-observer.d.ts.map +1 -1
- package/dist/core/monitor/artifact-observer.js +28 -78
- package/dist/core/monitor/artifact-observer.js.map +1 -1
- package/dist/core/monitor/divergence-map.d.ts.map +1 -1
- package/dist/core/monitor/divergence-map.js +9 -7
- package/dist/core/monitor/divergence-map.js.map +1 -1
- package/dist/core/monitor/health-verdict.d.ts.map +1 -1
- package/dist/core/monitor/health-verdict.js +2 -1
- package/dist/core/monitor/health-verdict.js.map +1 -1
- package/dist/core/monitor/trace-store.d.ts +1 -1
- package/dist/core/monitor/trace-store.js +2 -2
- package/dist/core/monitor/trace-store.js.map +1 -1
- package/dist/core/monitor/types.d.ts +1 -1
- package/dist/core/monitor/types.d.ts.map +1 -1
- package/dist/core/monitor/types.js +0 -1
- package/dist/core/monitor/types.js.map +1 -1
- package/dist/core/schemas/archive-summary.d.ts +39 -109
- package/dist/core/schemas/archive-summary.d.ts.map +1 -1
- package/dist/core/schemas/archive-summary.js +15 -23
- package/dist/core/schemas/archive-summary.js.map +1 -1
- package/dist/core/templates/commands/ack-confirm.md +1 -1
- package/dist/core/templates/commands/apply.md +51 -114
- package/dist/core/templates/commands/archive.md +43 -160
- package/dist/core/templates/commands/review.md +49 -74
- package/dist/core/templates/commands/upgrade.md +21 -40
- package/dist/core/templates/commands/verify.md +43 -146
- package/dist/core/templates/skills/_shared/tier23-command-bridge.md +34 -0
- package/dist/core/templates/skills/exploring.md +3 -3
- package/dist/core/templates/skills/finishing-a-development-branch.md +6 -21
- package/dist/core/templates/skills/receiving-code-review.md +14 -45
- package/dist/core/templates/skills/requesting-code-review.md +11 -0
- package/dist/core/templates/skills/subagent-driven-development.md +33 -29
- package/dist/core/templates/skills/subagent-driven-discipline.md +28 -28
- package/dist/core/templates/skills/using-forge.md +25 -24
- package/dist/core/templates/skills/verification-before-completion.md +7 -18
- package/dist/core/templates/skills/verifying-three-dimensions.md +93 -169
- package/dist/core/templates/skills/writing-plans.md +6 -17
- package/dist/core/validate/archive-summary-schema.d.ts +1 -1
- package/dist/core/validate/archive-summary-schema.d.ts.map +1 -1
- package/dist/core/validate/archive-summary-schema.js +101 -125
- package/dist/core/validate/archive-summary-schema.js.map +1 -1
- package/dist/core/validate/index.d.ts +0 -1
- package/dist/core/validate/index.d.ts.map +1 -1
- package/dist/core/validate/index.js +2 -1
- package/dist/core/validate/index.js.map +1 -1
- package/dist/core/validate/marker-schema.d.ts +12 -4
- package/dist/core/validate/marker-schema.d.ts.map +1 -1
- package/dist/core/validate/marker-schema.js +98 -605
- package/dist/core/validate/marker-schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/scripts/codex-review-helper.mjs +337 -0
- package/src/core/codex-review/prompts/adversarial-default.md +105 -0
|
@@ -1,676 +1,169 @@
|
|
|
1
|
-
// Marker schema 类型校验 —
|
|
1
|
+
// Marker schema 类型校验 — v4 OpenSpec alignment 简版(plan-v4 Phase 1 Task 1.3)
|
|
2
|
+
//
|
|
3
|
+
// v4 BREAKING change:
|
|
4
|
+
// - 删 KNOWN_SCHEMAS 中 v1 schema + verify-failed/review-failed
|
|
5
|
+
// - 改 ISO_8601_RE 允许 ms 精度(V-1 修复)
|
|
6
|
+
// - 删 process_evidence / ack_log_* / verify_findings 复杂校验
|
|
7
|
+
// - 简化 PauseDecision schema 校验为 PauseDecisionSimple(5 字段)
|
|
2
8
|
import { ok, failed, mergeResults } from './types.js';
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
const KNOWN_SCHEMAS = [
|
|
9
|
-
'forge-verify/v1',
|
|
10
|
-
'forge-review/v1',
|
|
11
|
-
'forge-verify-failed/v1',
|
|
12
|
-
'forge-review-failed/v1',
|
|
13
|
-
];
|
|
14
|
-
// ISO 8601 UTC 格式正则(YYYY-MM-DDTHH:MM:SSZ)
|
|
15
|
-
const ISO_8601_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
16
|
-
// sha256: 前缀 + 64 位小写十六进制
|
|
17
|
-
const SHA256_RE = /^sha256:[a-f0-9]{64}$/;
|
|
18
|
-
// finding_hash:裸 64-hex(沿 9a finding-hash.ts:8,不带 sha256: 前缀)
|
|
19
|
-
const FINDING_HASH_RE = /^[a-f0-9]{64}$/;
|
|
20
|
-
// git commit hash:40 位小写十六进制
|
|
21
|
-
const GIT_HEAD_RE = /^[a-f0-9]{40}$/;
|
|
22
|
-
// plan-9j Task 1 新增:created_by_tool_version semver 校验
|
|
23
|
-
// 沿 archive-summary.ts SEMVER_RE 同模式(major.minor.patch[-prerelease][+build])
|
|
9
|
+
// 已知合法的 schema 名称(v4 BREAKING:v2)
|
|
10
|
+
const KNOWN_SCHEMAS = ['forge-verify/v2', 'forge-review/v2'];
|
|
11
|
+
// ISO 8601 UTC 格式正则 — v4 允许 ms 精度(沿 V-1 修复:YYYY-MM-DDTHH:MM:SS[.fff]Z)
|
|
12
|
+
const ISO_8601_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?Z$/;
|
|
13
|
+
// semver(created_by_tool_version 用)
|
|
24
14
|
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
|
|
25
|
-
// actor 合法值
|
|
26
|
-
const ACTOR_VALUES = new Set(['ai-agent', 'human-override']);
|
|
27
|
-
// review_outcomes.severity 合法值(S/C/L 简码,语义独立于 9a 三级 enum CRITICAL/WARNING/SUGGESTION)
|
|
28
|
-
// plan-9d Task 3 (v12 REG-IMPORT-001 修订):rename 自 SEVERITY_VALUES → REVIEW_OUTCOME_SEVERITY_CODES
|
|
29
|
-
// v3 BLOCKER 4(plan-9j):union 扩 simcode + 全名(沿 types.ts ReviewOutcome.severity)
|
|
30
|
-
const REVIEW_OUTCOME_SEVERITY_CODES = new Set([
|
|
31
|
-
'S',
|
|
32
|
-
'C',
|
|
33
|
-
'L', // v0.4 simcode(老 marker 兼容)
|
|
34
|
-
'CRITICAL',
|
|
35
|
-
'WARNING',
|
|
36
|
-
'SUGGESTION', // v1.0 全名(resign 后或 v1.0 native 写入)
|
|
37
|
-
]);
|
|
38
|
-
// verify_findings.dimension 合法值(沿 design §2.2.2 三维度 + plan-9g brainstorm v10 加 process_evidence)
|
|
39
|
-
const DIMENSION_VALUES = new Set([
|
|
40
|
-
'completeness',
|
|
41
|
-
'correctness',
|
|
42
|
-
'coherence',
|
|
43
|
-
'process_evidence', // plan-9g freeze-time WARNING 7/10(brainstorm spec §3 修订)
|
|
44
|
-
]);
|
|
45
|
-
// plan-9c Task 1 新增:pause_decisions.chosen_option 合法值(1 | 2 | 3 | 4)
|
|
46
|
-
const CHOSEN_OPTION_VALUES = new Set([1, 2, 3, 4]);
|
|
47
15
|
/**
|
|
48
|
-
* 校验 marker
|
|
49
|
-
*
|
|
50
|
-
*
|
|
16
|
+
* validateMarkerSchema — 校验 marker 文件的 schema 类型 + 必填字段
|
|
17
|
+
*
|
|
18
|
+
* v4 简版:仅校验
|
|
19
|
+
* - schema 字段是否在 KNOWN_SCHEMAS 中
|
|
20
|
+
* - verified_at / reviewed_at ISO 8601 (允许 ms)
|
|
21
|
+
* - verified_by / reviewed_by 字符串非空
|
|
22
|
+
* - pause_decisions 数组类型(若存在)+ 内字段类型
|
|
23
|
+
* - created_by_tool_version semver 格式(若存在)
|
|
24
|
+
*
|
|
25
|
+
* 不再校验:tasks_hash / content_hash / git / process_evidence / ack_log_* / verify_findings
|
|
26
|
+
* (v4 反加固协议砍后这些字段已从 marker 删除)
|
|
51
27
|
*/
|
|
52
|
-
export function validateMarkerSchema(
|
|
53
|
-
if (!
|
|
54
|
-
return failed({ artifact: 'marker', message: 'marker must be a YAML mapping', file });
|
|
55
|
-
}
|
|
56
|
-
const obj = m;
|
|
57
|
-
// 1. schema 字段必须是已知值之一
|
|
58
|
-
if (typeof obj.schema !== 'string' || !KNOWN_SCHEMAS.includes(obj.schema)) {
|
|
28
|
+
export function validateMarkerSchema(obj, file) {
|
|
29
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
59
30
|
return failed({
|
|
60
31
|
artifact: 'marker',
|
|
61
|
-
|
|
62
|
-
message: `schema must be one of: ${KNOWN_SCHEMAS.join(', ')}`,
|
|
32
|
+
message: 'marker 文件根必须是 object',
|
|
63
33
|
file,
|
|
64
34
|
});
|
|
65
35
|
}
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
results.push(checkTimestamp(obj, 'verified_at', file));
|
|
71
|
-
results.push(checkActor(obj, 'verified_by', file));
|
|
72
|
-
results.push(checkSha256(obj, 'tasks_hash', file));
|
|
73
|
-
results.push(checkSha256(obj, 'content_hash', file));
|
|
74
|
-
results.push(checkEvidenceArray(obj.evidence, file));
|
|
75
|
-
// plan-9d Task 3 新增:verify_findings 数组校验(可选 superset additive,老 marker 缺等价 [])
|
|
76
|
-
if (obj.verify_findings !== undefined) {
|
|
77
|
-
results.push(checkVerifyFindingsArray(obj.verify_findings, file));
|
|
78
|
-
}
|
|
79
|
-
// plan-9c Task 1 新增 — pause_decisions superset additive
|
|
80
|
-
if (obj.pause_decisions !== undefined) {
|
|
81
|
-
results.push(checkPauseDecisionsArray(obj.pause_decisions, file));
|
|
82
|
-
}
|
|
83
|
-
// plan-9j Task 1 新增:created_by_tool_version 可选 superset additive 字段
|
|
84
|
-
results.push(checkSemverString(obj, 'created_by_tool_version', file));
|
|
85
|
-
// v3 BLOCKER 2 新增:resigned_by_tool_version(沿 v2 选项 C — 区分 created vs resigned)
|
|
86
|
-
results.push(checkSemverString(obj, 'resigned_by_tool_version', file));
|
|
87
|
-
}
|
|
88
|
-
else if (schema === 'forge-review/v1') {
|
|
89
|
-
results.push(checkTimestamp(obj, 'reviewed_at', file));
|
|
90
|
-
results.push(checkActor(obj, 'reviewed_by', file));
|
|
91
|
-
results.push(checkSha256(obj, 'tasks_hash', file));
|
|
92
|
-
results.push(checkSha256(obj, 'content_hash', file));
|
|
93
|
-
results.push(checkGitInfo(obj.git, file));
|
|
94
|
-
results.push(checkReviewOutcomes(obj.review_outcomes, file));
|
|
95
|
-
// plan-9c Task 1 新增
|
|
96
|
-
if (obj.pause_decisions !== undefined) {
|
|
97
|
-
results.push(checkPauseDecisionsArray(obj.pause_decisions, file));
|
|
98
|
-
}
|
|
99
|
-
// plan-9j Task 1 新增:created_by_tool_version 可选 superset additive 字段
|
|
100
|
-
results.push(checkSemverString(obj, 'created_by_tool_version', file));
|
|
101
|
-
// v3 BLOCKER 2 新增:resigned_by_tool_version(沿 v2 选项 C — 区分 created vs resigned)
|
|
102
|
-
results.push(checkSemverString(obj, 'resigned_by_tool_version', file));
|
|
103
|
-
}
|
|
104
|
-
else if (schema === 'forge-verify-failed/v1') {
|
|
105
|
-
results.push(checkTimestamp(obj, 'failed_at', file));
|
|
106
|
-
results.push(checkStringArray(obj, 'reasons', file));
|
|
107
|
-
results.push(checkStringArray(obj, 'fake_completions', file));
|
|
108
|
-
results.push(checkStringArray(obj, 'appended_tasks', file));
|
|
109
|
-
// plan-9d Task 3 新增:verify_findings 数组校验(可选 superset)
|
|
110
|
-
if (obj.verify_findings !== undefined) {
|
|
111
|
-
results.push(checkVerifyFindingsArray(obj.verify_findings, file));
|
|
112
|
-
}
|
|
113
|
-
// plan-9c Task 1 新增
|
|
114
|
-
if (obj.pause_decisions !== undefined) {
|
|
115
|
-
results.push(checkPauseDecisionsArray(obj.pause_decisions, file));
|
|
116
|
-
}
|
|
117
|
-
// plan-9j Task 1 新增:created_by_tool_version 可选 superset additive 字段
|
|
118
|
-
results.push(checkSemverString(obj, 'created_by_tool_version', file));
|
|
119
|
-
// v3 BLOCKER 2 新增:resigned_by_tool_version(沿 v2 选项 C — 区分 created vs resigned)
|
|
120
|
-
results.push(checkSemverString(obj, 'resigned_by_tool_version', file));
|
|
121
|
-
}
|
|
122
|
-
else if (schema === 'forge-review-failed/v1') {
|
|
123
|
-
results.push(checkTimestamp(obj, 'failed_at', file));
|
|
124
|
-
if (!Array.isArray(obj.unresolved_outcomes)) {
|
|
125
|
-
results.push(failed({
|
|
126
|
-
artifact: 'marker',
|
|
127
|
-
field: 'unresolved_outcomes',
|
|
128
|
-
message: 'must be array',
|
|
129
|
-
file,
|
|
130
|
-
}));
|
|
131
|
-
}
|
|
132
|
-
results.push(checkStringArray(obj, 'appended_tasks', file));
|
|
133
|
-
// plan-9c Task 1 新增
|
|
134
|
-
if (obj.pause_decisions !== undefined) {
|
|
135
|
-
results.push(checkPauseDecisionsArray(obj.pause_decisions, file));
|
|
136
|
-
}
|
|
137
|
-
// plan-9j Task 1 新增:created_by_tool_version 可选 superset additive 字段
|
|
138
|
-
results.push(checkSemverString(obj, 'created_by_tool_version', file));
|
|
139
|
-
// v3 BLOCKER 2 新增:resigned_by_tool_version(沿 v2 选项 C — 区分 created vs resigned)
|
|
140
|
-
results.push(checkSemverString(obj, 'resigned_by_tool_version', file));
|
|
36
|
+
const m = obj;
|
|
37
|
+
// 1. schema 字段必填且合法
|
|
38
|
+
if (typeof m.schema !== 'string') {
|
|
39
|
+
return failed({ artifact: 'marker', field: 'schema', message: 'schema 必须是字符串', file });
|
|
141
40
|
}
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
// 校验 ISO 8601 UTC 时间戳字段
|
|
145
|
-
function checkTimestamp(obj, field, file) {
|
|
146
|
-
const v = obj[field];
|
|
147
|
-
if (typeof v !== 'string' || !ISO_8601_RE.test(v)) {
|
|
148
|
-
return failed({
|
|
149
|
-
artifact: 'marker',
|
|
150
|
-
field,
|
|
151
|
-
message: 'must be ISO 8601 UTC (YYYY-MM-DDTHH:MM:SSZ)',
|
|
152
|
-
file,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return ok();
|
|
156
|
-
}
|
|
157
|
-
// 校验 actor 字段(ai-agent 或 human-override)
|
|
158
|
-
function checkActor(obj, field, file) {
|
|
159
|
-
const v = obj[field];
|
|
160
|
-
if (typeof v !== 'string' || !ACTOR_VALUES.has(v)) {
|
|
41
|
+
if (!KNOWN_SCHEMAS.includes(m.schema)) {
|
|
161
42
|
return failed({
|
|
162
43
|
artifact: 'marker',
|
|
163
|
-
field,
|
|
164
|
-
message:
|
|
44
|
+
field: 'schema',
|
|
45
|
+
message: `schema 必须 ∈ {${KNOWN_SCHEMAS.join(', ')}},实际:${m.schema}`,
|
|
165
46
|
file,
|
|
166
47
|
});
|
|
167
48
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
// plan-9j Task 1 新增:校验 semver 字符串字段(可选字段,缺失等价 <1.0.0)
|
|
179
|
-
function checkSemverString(obj, field, file) {
|
|
180
|
-
const v = obj[field];
|
|
181
|
-
if (v === undefined) {
|
|
182
|
-
return ok(); // 字段缺失等价老 marker(<1.0.0),superset additive 兼容
|
|
183
|
-
}
|
|
184
|
-
if (typeof v !== 'string' || !SEMVER_RE.test(v)) {
|
|
185
|
-
return failed({
|
|
49
|
+
const results = [];
|
|
50
|
+
const kind = m.schema === 'forge-verify/v2' ? 'verify' : 'review';
|
|
51
|
+
// 2. timestamp 字段(verified_at / reviewed_at)
|
|
52
|
+
const atKey = kind === 'verify' ? 'verified_at' : 'reviewed_at';
|
|
53
|
+
results.push(checkTimestamp(m, atKey, file));
|
|
54
|
+
// 3. by 字段(verified_by / reviewed_by)— v4 不限定 enum,允许任意 string
|
|
55
|
+
const byKey = kind === 'verify' ? 'verified_by' : 'reviewed_by';
|
|
56
|
+
if (typeof m[byKey] !== 'string' || m[byKey].length === 0) {
|
|
57
|
+
results.push(failed({
|
|
186
58
|
artifact: 'marker',
|
|
187
|
-
field,
|
|
188
|
-
message:
|
|
59
|
+
field: byKey,
|
|
60
|
+
message: `${byKey} 必须是非空字符串`,
|
|
189
61
|
file,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
return ok();
|
|
193
|
-
}
|
|
194
|
-
// 校验字符串数组字段
|
|
195
|
-
function checkStringArray(obj, field, file) {
|
|
196
|
-
const v = obj[field];
|
|
197
|
-
if (!Array.isArray(v) || !v.every((x) => typeof x === 'string')) {
|
|
198
|
-
return failed({ artifact: 'marker', field, message: 'must be array of strings', file });
|
|
62
|
+
}));
|
|
199
63
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
function checkEvidenceArray(v, file) {
|
|
204
|
-
if (!Array.isArray(v)) {
|
|
205
|
-
return failed({ artifact: 'marker', field: 'evidence', message: 'must be array', file });
|
|
64
|
+
// 4. pause_decisions(可选,数组类型 only)
|
|
65
|
+
if (m.pause_decisions !== undefined) {
|
|
66
|
+
results.push(checkPauseDecisionsArray(m.pause_decisions, file));
|
|
206
67
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!e || typeof e !== 'object') {
|
|
211
|
-
results.push(failed({ artifact: 'marker', field: `evidence[${i}]`, message: 'must be object', file }));
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (typeof e.scenario_id !== 'string' || !e.scenario_id) {
|
|
215
|
-
results.push(failed({
|
|
216
|
-
artifact: 'marker',
|
|
217
|
-
field: `evidence[${i}].scenario_id`,
|
|
218
|
-
message: 'required string',
|
|
219
|
-
file,
|
|
220
|
-
}));
|
|
221
|
-
}
|
|
222
|
-
if (typeof e.test_command !== 'string') {
|
|
223
|
-
results.push(failed({
|
|
224
|
-
artifact: 'marker',
|
|
225
|
-
field: `evidence[${i}].test_command`,
|
|
226
|
-
message: 'required string',
|
|
227
|
-
file,
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
if (typeof e.test_file !== 'string') {
|
|
231
|
-
results.push(failed({
|
|
232
|
-
artifact: 'marker',
|
|
233
|
-
field: `evidence[${i}].test_file`,
|
|
234
|
-
message: 'required string',
|
|
235
|
-
file,
|
|
236
|
-
}));
|
|
237
|
-
}
|
|
238
|
-
// log_path 必须以 ./ 开头
|
|
239
|
-
if (typeof e.log_path !== 'string' || !e.log_path.startsWith('./')) {
|
|
68
|
+
// 5. created_by_tool_version(可选,semver 格式)
|
|
69
|
+
if (m.created_by_tool_version !== undefined) {
|
|
70
|
+
if (typeof m.created_by_tool_version !== 'string') {
|
|
240
71
|
results.push(failed({
|
|
241
72
|
artifact: 'marker',
|
|
242
|
-
field:
|
|
243
|
-
message: '
|
|
73
|
+
field: 'created_by_tool_version',
|
|
74
|
+
message: 'created_by_tool_version 必须是字符串',
|
|
244
75
|
file,
|
|
245
76
|
}));
|
|
246
77
|
}
|
|
247
|
-
if (
|
|
78
|
+
else if (!SEMVER_RE.test(m.created_by_tool_version)) {
|
|
248
79
|
results.push(failed({
|
|
249
80
|
artifact: 'marker',
|
|
250
|
-
field:
|
|
251
|
-
message:
|
|
252
|
-
file,
|
|
253
|
-
}));
|
|
254
|
-
}
|
|
255
|
-
if (typeof e.pass !== 'boolean') {
|
|
256
|
-
results.push(failed({
|
|
257
|
-
artifact: 'marker',
|
|
258
|
-
field: `evidence[${i}].pass`,
|
|
259
|
-
message: 'required boolean',
|
|
81
|
+
field: 'created_by_tool_version',
|
|
82
|
+
message: `created_by_tool_version 必须是 semver(X.Y.Z),实际:${m.created_by_tool_version}`,
|
|
260
83
|
file,
|
|
261
84
|
}));
|
|
262
85
|
}
|
|
263
86
|
}
|
|
264
|
-
return mergeResults(...results);
|
|
87
|
+
return results.length === 0 ? ok() : mergeResults(...results);
|
|
265
88
|
}
|
|
266
|
-
|
|
267
|
-
function
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
const g = v;
|
|
272
|
-
if (typeof g.is_git_repo !== 'boolean') {
|
|
89
|
+
/** 校验 ISO 8601 UTC 时间戳字段(允许 ms 精度) */
|
|
90
|
+
function checkTimestamp(obj, field, file) {
|
|
91
|
+
const v = obj[field];
|
|
92
|
+
if (typeof v !== 'string') {
|
|
273
93
|
return failed({
|
|
274
94
|
artifact: 'marker',
|
|
275
|
-
field
|
|
276
|
-
message:
|
|
95
|
+
field,
|
|
96
|
+
message: `${field} 必须是字符串`,
|
|
277
97
|
file,
|
|
278
98
|
});
|
|
279
99
|
}
|
|
280
|
-
|
|
281
|
-
if (g.is_git_repo) {
|
|
282
|
-
if (typeof g.head !== 'string' || !GIT_HEAD_RE.test(g.head)) {
|
|
283
|
-
return failed({
|
|
284
|
-
artifact: 'marker',
|
|
285
|
-
field: 'git.head',
|
|
286
|
-
message: 'must match ^[a-f0-9]{40}$',
|
|
287
|
-
file,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (typeof g.diff_hash !== 'string' || !SHA256_RE.test(g.diff_hash)) {
|
|
291
|
-
return failed({
|
|
292
|
-
artifact: 'marker',
|
|
293
|
-
field: 'git.diff_hash',
|
|
294
|
-
message: 'must match sha256',
|
|
295
|
-
file,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
if (!g.diff_pathspec || typeof g.diff_pathspec !== 'object') {
|
|
299
|
-
return failed({
|
|
300
|
-
artifact: 'marker',
|
|
301
|
-
field: 'git.diff_pathspec',
|
|
302
|
-
message: 'required object',
|
|
303
|
-
file,
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
// git.diff_pathspec.include / exclude 必须是 string array(spec §3.4.1)
|
|
307
|
-
const ps = g.diff_pathspec;
|
|
308
|
-
if (!Array.isArray(ps.include) || !ps.include.every((x) => typeof x === 'string')) {
|
|
309
|
-
return failed({
|
|
310
|
-
artifact: 'marker',
|
|
311
|
-
field: 'git.diff_pathspec.include',
|
|
312
|
-
message: 'must be array of strings',
|
|
313
|
-
file,
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
if (!Array.isArray(ps.exclude) || !ps.exclude.every((x) => typeof x === 'string')) {
|
|
317
|
-
return failed({
|
|
318
|
-
artifact: 'marker',
|
|
319
|
-
field: 'git.diff_pathspec.exclude',
|
|
320
|
-
message: 'must be array of strings',
|
|
321
|
-
file,
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return ok();
|
|
326
|
-
}
|
|
327
|
-
// 校验 review_outcomes 数组
|
|
328
|
-
function checkReviewOutcomes(v, file) {
|
|
329
|
-
if (!Array.isArray(v)) {
|
|
330
|
-
return failed({ artifact: 'marker', field: 'review_outcomes', message: 'must be array', file });
|
|
331
|
-
}
|
|
332
|
-
const results = [];
|
|
333
|
-
for (let i = 0; i < v.length; i++) {
|
|
334
|
-
const o = v[i];
|
|
335
|
-
if (!o || typeof o !== 'object') {
|
|
336
|
-
results.push(failed({
|
|
337
|
-
artifact: 'marker',
|
|
338
|
-
field: `review_outcomes[${i}]`,
|
|
339
|
-
message: 'must be object',
|
|
340
|
-
file,
|
|
341
|
-
}));
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
// severity 必须是 6 元素之一(v0.4 simcode S/C/L + v1.0 全名 CRITICAL/WARNING/SUGGESTION;沿 plan-9j v3 BLOCKER 4 双格式扩)
|
|
345
|
-
if (typeof o.severity !== 'string' || !REVIEW_OUTCOME_SEVERITY_CODES.has(o.severity)) {
|
|
346
|
-
results.push(failed({
|
|
347
|
-
artifact: 'marker',
|
|
348
|
-
field: `review_outcomes[${i}].severity`,
|
|
349
|
-
message: 'must be one of: S / C / L (v0.4 simcode) or CRITICAL / WARNING / SUGGESTION (v1.0 全名)',
|
|
350
|
-
file,
|
|
351
|
-
}));
|
|
352
|
-
}
|
|
353
|
-
if (typeof o.accepted !== 'boolean') {
|
|
354
|
-
results.push(failed({
|
|
355
|
-
artifact: 'marker',
|
|
356
|
-
field: `review_outcomes[${i}].accepted`,
|
|
357
|
-
message: 'required boolean',
|
|
358
|
-
file,
|
|
359
|
-
}));
|
|
360
|
-
}
|
|
361
|
-
if (typeof o.resolved !== 'boolean') {
|
|
362
|
-
results.push(failed({
|
|
363
|
-
artifact: 'marker',
|
|
364
|
-
field: `review_outcomes[${i}].resolved`,
|
|
365
|
-
message: 'required boolean',
|
|
366
|
-
file,
|
|
367
|
-
}));
|
|
368
|
-
}
|
|
369
|
-
// accepted=true 必须有 task_ref;accepted=false 必须有 rationale
|
|
370
|
-
if (o.accepted === true && (typeof o.task_ref !== 'string' || !o.task_ref)) {
|
|
371
|
-
results.push(failed({
|
|
372
|
-
artifact: 'marker',
|
|
373
|
-
field: `review_outcomes[${i}].task_ref`,
|
|
374
|
-
message: 'required when accepted=true',
|
|
375
|
-
file,
|
|
376
|
-
}));
|
|
377
|
-
}
|
|
378
|
-
if (o.accepted === false && (typeof o.rationale !== 'string' || !o.rationale)) {
|
|
379
|
-
results.push(failed({
|
|
380
|
-
artifact: 'marker',
|
|
381
|
-
field: `review_outcomes[${i}].rationale`,
|
|
382
|
-
message: 'required when accepted=false',
|
|
383
|
-
file,
|
|
384
|
-
}));
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return mergeResults(...results);
|
|
388
|
-
}
|
|
389
|
-
// plan-9d Task 3:verify_findings 数组校验
|
|
390
|
-
// 校验范围:必填字段存在性 + 类型 + 枚举值(沿 master §3.12.1)
|
|
391
|
-
// 不校验业务规则(resolved/ack 矩阵 + finding_hash 与 payload 一致性) — 那是 Task 6 fence 的事
|
|
392
|
-
function checkVerifyFindingsArray(v, file) {
|
|
393
|
-
if (!Array.isArray(v)) {
|
|
100
|
+
if (!ISO_8601_RE.test(v)) {
|
|
394
101
|
return failed({
|
|
395
102
|
artifact: 'marker',
|
|
396
|
-
field
|
|
397
|
-
message:
|
|
103
|
+
field,
|
|
104
|
+
message: `${field} 必须是 ISO 8601 UTC (YYYY-MM-DDTHH:MM:SS[.fff]Z),实际:${v}`,
|
|
398
105
|
file,
|
|
399
106
|
});
|
|
400
107
|
}
|
|
401
|
-
|
|
402
|
-
// v4 D-2 修订:finding id 唯一性校验(同一 marker 内不允许重复 id,沿 master §3.12.1)
|
|
403
|
-
const seenIds = new Set();
|
|
404
|
-
for (let i = 0; i < v.length; i++) {
|
|
405
|
-
const f = v[i];
|
|
406
|
-
if (typeof f?.id === 'number') {
|
|
407
|
-
if (seenIds.has(f.id)) {
|
|
408
|
-
results.push(failed({
|
|
409
|
-
artifact: 'marker',
|
|
410
|
-
field: `verify_findings[${i}].id`,
|
|
411
|
-
message: `finding id ${f.id} 重复(同一 marker 内 id 必须唯一,沿 master §3.12.1)`,
|
|
412
|
-
file,
|
|
413
|
-
}));
|
|
414
|
-
}
|
|
415
|
-
seenIds.add(f.id);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
for (let i = 0; i < v.length; i++) {
|
|
419
|
-
const f = v[i];
|
|
420
|
-
if (!f || typeof f !== 'object') {
|
|
421
|
-
results.push(failed({
|
|
422
|
-
artifact: 'marker',
|
|
423
|
-
field: `verify_findings[${i}]`,
|
|
424
|
-
message: 'must be object',
|
|
425
|
-
file,
|
|
426
|
-
}));
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
// 必填 11 字段(沿 master §3.12.1 Finding):
|
|
430
|
-
// id / dimension / check_type / severity / automated / evidence / recommendation / resolved
|
|
431
|
-
// + finding_hash / content_hash / git_head
|
|
432
|
-
if (typeof f.id !== 'number') {
|
|
433
|
-
results.push(failed({
|
|
434
|
-
artifact: 'marker',
|
|
435
|
-
field: `verify_findings[${i}].id`,
|
|
436
|
-
message: 'required number',
|
|
437
|
-
file,
|
|
438
|
-
}));
|
|
439
|
-
}
|
|
440
|
-
if (typeof f.dimension !== 'string' || !DIMENSION_VALUES.has(f.dimension)) {
|
|
441
|
-
results.push(failed({
|
|
442
|
-
artifact: 'marker',
|
|
443
|
-
field: `verify_findings[${i}].dimension`,
|
|
444
|
-
message: 'must be completeness|correctness|coherence|process_evidence',
|
|
445
|
-
file,
|
|
446
|
-
}));
|
|
447
|
-
}
|
|
448
|
-
if (typeof f.check_type !== 'string' || !f.check_type) {
|
|
449
|
-
results.push(failed({
|
|
450
|
-
artifact: 'marker',
|
|
451
|
-
field: `verify_findings[${i}].check_type`,
|
|
452
|
-
message: 'required string',
|
|
453
|
-
file,
|
|
454
|
-
}));
|
|
455
|
-
}
|
|
456
|
-
if (!isSeverity(f.severity)) {
|
|
457
|
-
results.push(failed({
|
|
458
|
-
artifact: 'marker',
|
|
459
|
-
field: `verify_findings[${i}].severity`,
|
|
460
|
-
message: `must be one of ${[...SEVERITY_VALUES].join(', ')}`,
|
|
461
|
-
file,
|
|
462
|
-
}));
|
|
463
|
-
}
|
|
464
|
-
if (typeof f.automated !== 'boolean') {
|
|
465
|
-
results.push(failed({
|
|
466
|
-
artifact: 'marker',
|
|
467
|
-
field: `verify_findings[${i}].automated`,
|
|
468
|
-
message: 'required boolean',
|
|
469
|
-
file,
|
|
470
|
-
}));
|
|
471
|
-
}
|
|
472
|
-
if (typeof f.evidence !== 'string' || !f.evidence) {
|
|
473
|
-
results.push(failed({
|
|
474
|
-
artifact: 'marker',
|
|
475
|
-
field: `verify_findings[${i}].evidence`,
|
|
476
|
-
message: 'required non-empty string',
|
|
477
|
-
file,
|
|
478
|
-
}));
|
|
479
|
-
}
|
|
480
|
-
if (typeof f.recommendation !== 'string' || !f.recommendation) {
|
|
481
|
-
results.push(failed({
|
|
482
|
-
artifact: 'marker',
|
|
483
|
-
field: `verify_findings[${i}].recommendation`,
|
|
484
|
-
message: 'required non-empty string',
|
|
485
|
-
file,
|
|
486
|
-
}));
|
|
487
|
-
}
|
|
488
|
-
if (typeof f.resolved !== 'boolean') {
|
|
489
|
-
results.push(failed({
|
|
490
|
-
artifact: 'marker',
|
|
491
|
-
field: `verify_findings[${i}].resolved`,
|
|
492
|
-
message: 'required boolean',
|
|
493
|
-
file,
|
|
494
|
-
}));
|
|
495
|
-
}
|
|
496
|
-
// plan-9d v2 B-1 修订:finding_hash 是裸 64-hex(沿 9a finding-hash.ts:8)
|
|
497
|
-
if (typeof f.finding_hash !== 'string' || !FINDING_HASH_RE.test(f.finding_hash)) {
|
|
498
|
-
results.push(failed({
|
|
499
|
-
artifact: 'marker',
|
|
500
|
-
field: `verify_findings[${i}].finding_hash`,
|
|
501
|
-
message: 'must match ^[a-f0-9]{64}$ (裸 hex,无 sha256: 前缀,沿 9a)',
|
|
502
|
-
file,
|
|
503
|
-
}));
|
|
504
|
-
}
|
|
505
|
-
// content_hash / git_head 是 FindingHashPayload 字段(沿 9a;content_hash sha256: 前缀,git_head 裸 40-hex)
|
|
506
|
-
if (typeof f.content_hash !== 'string' || !SHA256_RE.test(f.content_hash)) {
|
|
507
|
-
results.push(failed({
|
|
508
|
-
artifact: 'marker',
|
|
509
|
-
field: `verify_findings[${i}].content_hash`,
|
|
510
|
-
message: 'must match ^sha256:[a-f0-9]{64}$',
|
|
511
|
-
file,
|
|
512
|
-
}));
|
|
513
|
-
}
|
|
514
|
-
if (typeof f.git_head !== 'string' || !GIT_HEAD_RE.test(f.git_head)) {
|
|
515
|
-
results.push(failed({
|
|
516
|
-
artifact: 'marker',
|
|
517
|
-
field: `verify_findings[${i}].git_head`,
|
|
518
|
-
message: 'must match ^[a-f0-9]{40}$',
|
|
519
|
-
file,
|
|
520
|
-
}));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
return mergeResults(...results);
|
|
108
|
+
return ok();
|
|
524
109
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
function checkPauseDecisionsArray(v, file) {
|
|
529
|
-
if (!Array.isArray(v)) {
|
|
110
|
+
/** 校验 pause_decisions 数组(v4 简化版,无 fence 业务校验) */
|
|
111
|
+
function checkPauseDecisionsArray(value, file) {
|
|
112
|
+
if (!Array.isArray(value)) {
|
|
530
113
|
return failed({
|
|
531
114
|
artifact: 'marker',
|
|
532
115
|
field: 'pause_decisions',
|
|
533
|
-
message: '
|
|
116
|
+
message: 'pause_decisions 必须是数组(若存在)',
|
|
534
117
|
file,
|
|
535
118
|
});
|
|
536
119
|
}
|
|
537
120
|
const results = [];
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (seenIds.has(p.id)) {
|
|
544
|
-
results.push(failed({
|
|
545
|
-
artifact: 'marker',
|
|
546
|
-
field: `pause_decisions[${i}].id`,
|
|
547
|
-
message: `pause_decision id ${p.id} 重复(同一 marker 内 id 必须唯一)`,
|
|
548
|
-
file,
|
|
549
|
-
}));
|
|
550
|
-
}
|
|
551
|
-
seenIds.add(p.id);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
for (let i = 0; i < v.length; i++) {
|
|
555
|
-
const p = v[i];
|
|
556
|
-
if (!p || typeof p !== 'object') {
|
|
557
|
-
results.push(failed({
|
|
558
|
-
artifact: 'marker',
|
|
559
|
-
field: `pause_decisions[${i}]`,
|
|
560
|
-
message: 'must be object',
|
|
561
|
-
file,
|
|
562
|
-
}));
|
|
121
|
+
for (let i = 0; i < value.length; i++) {
|
|
122
|
+
const p = value[i];
|
|
123
|
+
const base = `pause_decisions[${i}]`;
|
|
124
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) {
|
|
125
|
+
results.push(failed({ artifact: 'marker', field: base, message: 'pause_decision 必须是 object', file }));
|
|
563
126
|
continue;
|
|
564
127
|
}
|
|
565
|
-
|
|
566
|
-
// 必填 8 字段(沿 design §2.1.5,v3 codex NIT 11 修订:7 → 8):id / paused_at / task_ref / issue_summary / severity / chosen_option / target_artifact / target_anchor
|
|
567
|
-
if (typeof p.id !== 'number') {
|
|
568
|
-
results.push(failed({ artifact: 'marker', field: `${fieldBase}.id`, message: 'required number', file }));
|
|
569
|
-
}
|
|
128
|
+
// paused_at
|
|
570
129
|
if (typeof p.paused_at !== 'string' || !ISO_8601_RE.test(p.paused_at)) {
|
|
571
130
|
results.push(failed({
|
|
572
131
|
artifact: 'marker',
|
|
573
|
-
field: `${
|
|
574
|
-
message: '
|
|
575
|
-
file,
|
|
576
|
-
}));
|
|
577
|
-
}
|
|
578
|
-
if (typeof p.task_ref !== 'string' || !p.task_ref) {
|
|
579
|
-
results.push(failed({
|
|
580
|
-
artifact: 'marker',
|
|
581
|
-
field: `${fieldBase}.task_ref`,
|
|
582
|
-
message: 'required string (e.g., tasks.md#task-3)',
|
|
583
|
-
file,
|
|
584
|
-
}));
|
|
585
|
-
}
|
|
586
|
-
if (typeof p.issue_summary !== 'string' || !p.issue_summary) {
|
|
587
|
-
results.push(failed({
|
|
588
|
-
artifact: 'marker',
|
|
589
|
-
field: `${fieldBase}.issue_summary`,
|
|
590
|
-
message: 'required non-empty string',
|
|
591
|
-
file,
|
|
592
|
-
}));
|
|
593
|
-
}
|
|
594
|
-
if (!isSeverity(p.severity)) {
|
|
595
|
-
results.push(failed({
|
|
596
|
-
artifact: 'marker',
|
|
597
|
-
field: `${fieldBase}.severity`,
|
|
598
|
-
message: `must be one of ${[...SEVERITY_VALUES].join(', ')}`,
|
|
132
|
+
field: `${base}.paused_at`,
|
|
133
|
+
message: 'paused_at 必须是 ISO 8601 UTC (YYYY-MM-DDTHH:MM:SS[.fff]Z)',
|
|
599
134
|
file,
|
|
600
135
|
}));
|
|
601
136
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
artifact: 'marker',
|
|
613
|
-
field: `${fieldBase}.target_artifact`,
|
|
614
|
-
message: 'required string (e.g., proposal.md / tasks.md / design.md)',
|
|
615
|
-
file,
|
|
616
|
-
}));
|
|
137
|
+
// task_ref / issue_summary 必填 string
|
|
138
|
+
for (const k of ['task_ref', 'issue_summary']) {
|
|
139
|
+
if (typeof p[k] !== 'string' || p[k].length === 0) {
|
|
140
|
+
results.push(failed({
|
|
141
|
+
artifact: 'marker',
|
|
142
|
+
field: `${base}.${k}`,
|
|
143
|
+
message: `${k} 必须是非空字符串`,
|
|
144
|
+
file,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
617
147
|
}
|
|
618
|
-
|
|
148
|
+
// chosen_option ∈ {1, 2, 3, 4}
|
|
149
|
+
if (typeof p.chosen_option !== 'number' || ![1, 2, 3, 4].includes(p.chosen_option)) {
|
|
619
150
|
results.push(failed({
|
|
620
151
|
artifact: 'marker',
|
|
621
|
-
field: `${
|
|
622
|
-
message: '
|
|
152
|
+
field: `${base}.chosen_option`,
|
|
153
|
+
message: 'chosen_option 必须 ∈ {1, 2, 3, 4}',
|
|
623
154
|
file,
|
|
624
155
|
}));
|
|
625
156
|
}
|
|
626
|
-
//
|
|
627
|
-
if (p.
|
|
628
|
-
(typeof p.severity_acked_by !== 'string' || !p.severity_acked_by)) {
|
|
157
|
+
// notes 可选 string
|
|
158
|
+
if (p.notes !== undefined && typeof p.notes !== 'string') {
|
|
629
159
|
results.push(failed({
|
|
630
160
|
artifact: 'marker',
|
|
631
|
-
field: `${
|
|
632
|
-
message: '
|
|
161
|
+
field: `${base}.notes`,
|
|
162
|
+
message: 'notes 必须是字符串(若存在)',
|
|
633
163
|
file,
|
|
634
164
|
}));
|
|
635
165
|
}
|
|
636
|
-
if (p.severity_acked_at !== null) {
|
|
637
|
-
if (typeof p.severity_acked_at !== 'string' || !ISO_8601_RE.test(p.severity_acked_at)) {
|
|
638
|
-
results.push(failed({
|
|
639
|
-
artifact: 'marker',
|
|
640
|
-
field: `${fieldBase}.severity_acked_at`,
|
|
641
|
-
message: 'must be null or ISO 8601 UTC',
|
|
642
|
-
file,
|
|
643
|
-
}));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// non_blocking_rationale / other_rationale / other_acked_by 类型校验(null 或 string;业务规则在 fence)
|
|
647
|
-
for (const field of ['non_blocking_rationale', 'other_rationale', 'other_acked_by']) {
|
|
648
|
-
const val = p[field];
|
|
649
|
-
if (val !== null && (typeof val !== 'string' || !val)) {
|
|
650
|
-
results.push(failed({
|
|
651
|
-
artifact: 'marker',
|
|
652
|
-
field: `${fieldBase}.${field}`,
|
|
653
|
-
message: 'must be null or non-empty string',
|
|
654
|
-
file,
|
|
655
|
-
}));
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
// added_task_ref / capture_id:undefined(老 marker)跳过,
|
|
659
|
-
// 存在则须 null 或 non-empty string(schema 层 optional,superset additive)
|
|
660
|
-
for (const field of ['added_task_ref', 'capture_id']) {
|
|
661
|
-
const val = p[field];
|
|
662
|
-
if (val === undefined)
|
|
663
|
-
continue; // superset additive:老 marker 缺字段 → 跳过(legacy 分支)
|
|
664
|
-
if (val !== null && (typeof val !== 'string' || !val)) {
|
|
665
|
-
results.push(failed({
|
|
666
|
-
artifact: 'marker',
|
|
667
|
-
field: `${fieldBase}.${field}`,
|
|
668
|
-
message: 'must be null or non-empty string',
|
|
669
|
-
file,
|
|
670
|
-
}));
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
166
|
}
|
|
674
|
-
return mergeResults(...results);
|
|
167
|
+
return results.length === 0 ? ok() : mergeResults(...results);
|
|
675
168
|
}
|
|
676
169
|
//# sourceMappingURL=marker-schema.js.map
|