release-skill 0.2.3 → 0.2.4
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +27 -0
- package/INSTALL.md +95 -139
- package/INSTALL.zh-CN.md +70 -121
- package/README.md +264 -913
- package/README.zh-CN.md +222 -535
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/claude/schemas/release-plan.schema.json +137 -0
- package/adapters/claude/schemas/release-project.schema.json +93 -0
- package/adapters/claude/schemas/release-run.schema.json +70 -2
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/codex/schemas/release-plan.schema.json +137 -0
- package/adapters/codex/schemas/release-project.schema.json +93 -0
- package/adapters/codex/schemas/release-run.schema.json +70 -2
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/kimi/schemas/release-plan.schema.json +137 -0
- package/adapters/kimi/schemas/release-project.schema.json +93 -0
- package/adapters/kimi/schemas/release-run.schema.json +70 -2
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +19054 -17573
- package/adapters/workbuddy/schemas/release-plan.schema.json +137 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +93 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +70 -2
- package/bin/release-skill.bundle.mjs +19054 -17573
- package/package.json +1 -1
- package/schemas/release-plan.schema.json +137 -0
- package/schemas/release-project.schema.json +93 -0
- package/schemas/release-run.schema.json +70 -2
- package/src/adapters/plugin-marketplace.mjs +1190 -435
- package/src/commands/prepare.mjs +380 -40
- package/src/commands/publish.mjs +107 -75
- package/src/commands/reconcile.mjs +92 -327
- package/src/commands/setup.mjs +148 -20
- package/src/commands/verify.mjs +304 -20
- package/src/core/baseline.mjs +8 -1
- package/src/core/checkpoints.mjs +50 -7
- package/src/core/config.mjs +15 -0
- package/src/core/errors.mjs +2 -0
- package/src/core/installation-contract.mjs +341 -0
- package/src/core/plan.mjs +129 -6
- package/src/platforms/codebuddy.mjs +193 -280
- package/src/platforms/codex.mjs +369 -0
- package/src/platforms/kimi.mjs +164 -119
- package/src/platforms/registry.mjs +24 -6
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex platform human-attestation fallback strategy.
|
|
3
|
+
*
|
|
4
|
+
* Codex is primarily an automatable platform (structured-cli), but its
|
|
5
|
+
* degradationPolicy is 'human-attestation-with-fallback': when the CLI
|
|
6
|
+
* interface, environment, or transport is unavailable, the system falls back
|
|
7
|
+
* to a human attestation path. Explicit mismatches (identity, version,
|
|
8
|
+
* payload, marketplace entry) are hard failures and never trigger fallback.
|
|
9
|
+
*
|
|
10
|
+
* 统一人工结果:收据仅需 platform, version, planDigest,
|
|
11
|
+
* result(passed|failed), actor, confirmedAt 和可选 note。
|
|
12
|
+
* 不再要求 consumer, plugin, conclusion, confirmedBy、隔离 HOME、
|
|
13
|
+
* 安装路径证明、载荷摘要手填或 24 小时过期。
|
|
14
|
+
*
|
|
15
|
+
* @module platforms/codex
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import { join, resolve, relative, isAbsolute } from 'node:path';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
ActionType,
|
|
23
|
+
ActionStatus,
|
|
24
|
+
createResult,
|
|
25
|
+
resolveTimeoutMs,
|
|
26
|
+
SAFE_ID_RE,
|
|
27
|
+
writeEvidenceAtomic,
|
|
28
|
+
} from '../adapters/contract.mjs';
|
|
29
|
+
import { canonicalJson } from '../core/digest.mjs';
|
|
30
|
+
|
|
31
|
+
/** 64-char lowercase hex plan/payload digest pattern. */
|
|
32
|
+
const HEX_DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Normalize a plan back to its frozen form for digest comparison.
|
|
36
|
+
* Only lifecycle status fields are reset: the top-level `status` returns to
|
|
37
|
+
* "PREPARED" and every `externalActions[].status` returns to "PENDING".
|
|
38
|
+
*
|
|
39
|
+
* @param {object} plan
|
|
40
|
+
* @returns {object} the lifecycle-normalized plan
|
|
41
|
+
*/
|
|
42
|
+
function normalizePlanForDigest(plan) {
|
|
43
|
+
const normalized = { ...plan, status: 'PREPARED' };
|
|
44
|
+
if (Array.isArray(plan.externalActions)) {
|
|
45
|
+
normalized.externalActions = plan.externalActions.map((action) => (
|
|
46
|
+
action && typeof action === 'object' && !Array.isArray(action)
|
|
47
|
+
? { ...action, status: 'PENDING' }
|
|
48
|
+
: action
|
|
49
|
+
));
|
|
50
|
+
}
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Structured manual-install requirement written by codex fallback execute. */
|
|
55
|
+
export const CODEX_REQUIREMENT_FILE = 'release-skill-codex-manual-install.json';
|
|
56
|
+
/** Structured human attestation consumed by codex fallback observe. */
|
|
57
|
+
export const CODEX_ATTESTATION_FILE = 'release-skill-codex-attestation.json';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve and verify the genuine frozen plan digest from the adapter context,
|
|
61
|
+
* for the codex human-attestation fallback path.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} context - adapter context (must carry the frozen `plan`).
|
|
64
|
+
* @returns {Promise<string>} the verified frozen plan digest.
|
|
65
|
+
* @throws {Error} when the plan is absent or the digest does not match.
|
|
66
|
+
*/
|
|
67
|
+
export async function resolveCodexBoundPlanDigest(context) {
|
|
68
|
+
const plan = context?.plan;
|
|
69
|
+
if (!plan || typeof plan !== 'object' || Array.isArray(plan)) {
|
|
70
|
+
throw new Error('context.plan is required to bind the codex plan digest');
|
|
71
|
+
}
|
|
72
|
+
const carried = plan.digest;
|
|
73
|
+
if (typeof carried !== 'string' || !HEX_DIGEST_RE.test(carried)) {
|
|
74
|
+
throw new Error('context.plan.digest must be a 64-char lowercase hex frozen plan digest');
|
|
75
|
+
}
|
|
76
|
+
// Lazy import keeps the static bundle graph acyclic.
|
|
77
|
+
const { computePlanDigest } = await import('../core/plan.mjs');
|
|
78
|
+
const normalized = normalizePlanForDigest(plan);
|
|
79
|
+
if (computePlanDigest(normalized) !== carried) {
|
|
80
|
+
throw new Error('context.plan.digest does not match the normalized frozen plan (a non-lifecycle field was tampered)');
|
|
81
|
+
}
|
|
82
|
+
return carried;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Authoritative, cross-run attestation directory for a codex fallback install.
|
|
87
|
+
*
|
|
88
|
+
* Lives at a stable root-fixed location keyed by the verified frozen plan
|
|
89
|
+
* digest and plugin id:
|
|
90
|
+
* <root>/.release-skill/codex-attestations/<planDigest>/<plugin>/
|
|
91
|
+
*
|
|
92
|
+
* @param {object} context - adapter context (needs `root`).
|
|
93
|
+
* @param {string} planDigest - verified frozen plan digest (64-hex).
|
|
94
|
+
* @param {string} plugin - plugin id (SAFE_ID_RE).
|
|
95
|
+
* @returns {string} absolute authority directory.
|
|
96
|
+
*/
|
|
97
|
+
export function codexAuthorityDir(context, planDigest, plugin) {
|
|
98
|
+
if (!context?.root) {
|
|
99
|
+
throw new Error('context.root is required for the codex attestation authority');
|
|
100
|
+
}
|
|
101
|
+
if (!HEX_DIGEST_RE.test(planDigest)) {
|
|
102
|
+
throw new Error('codex attestation authority requires a 64-hex plan digest');
|
|
103
|
+
}
|
|
104
|
+
if (!SAFE_ID_RE.test(plugin)) {
|
|
105
|
+
throw new Error(`codex attestation authority requires a safe plugin id: "${plugin}"`);
|
|
106
|
+
}
|
|
107
|
+
const base = resolve(context.root, '.release-skill', 'codex-attestations');
|
|
108
|
+
const dir = resolve(base, planDigest, plugin);
|
|
109
|
+
const rel = relative(base, dir);
|
|
110
|
+
const sep = process.platform === 'win32' ? '\\' : '/';
|
|
111
|
+
if (
|
|
112
|
+
rel === '' || rel === '..' || isAbsolute(rel) || rel.startsWith(`..${sep}`)
|
|
113
|
+
|| rel.split(sep).some((segment) => segment === '..' || segment === '')
|
|
114
|
+
) {
|
|
115
|
+
throw new Error('codex attestation authority path escapes its base');
|
|
116
|
+
}
|
|
117
|
+
return dir;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 统一人工结果验证:验证 codex 人工结果是否匹配冻结计划。
|
|
122
|
+
*
|
|
123
|
+
* 统一后的结果只需:
|
|
124
|
+
* - 必填:platform, version, planDigest, result(passed|failed), actor, confirmedAt
|
|
125
|
+
* - 可选:note
|
|
126
|
+
*
|
|
127
|
+
* 绑定验证(任何不匹配都失败):
|
|
128
|
+
* - planDigest 绑定到真正的冻结计划摘要(boundPlanDigest)
|
|
129
|
+
* - version 静态一致性检查
|
|
130
|
+
* - result 只接受 passed 或 failed
|
|
131
|
+
*
|
|
132
|
+
* @param {object} attestation - 解析后的人工结果 JSON。
|
|
133
|
+
* @param {object} action - 展开的 codex 动作(顶层字段)。
|
|
134
|
+
* @param {string} isoNow - 当前 ISO 时间戳(保留签名兼容,不再用于过期检查)。
|
|
135
|
+
* @param {string} boundPlanDigest - 验证过的冻结计划摘要。
|
|
136
|
+
* @returns {{valid:boolean, error:string|null}}
|
|
137
|
+
*/
|
|
138
|
+
export function validateCodexAttestation(attestation, action, isoNow, boundPlanDigest) {
|
|
139
|
+
if (!attestation || typeof attestation !== 'object' || Array.isArray(attestation)) {
|
|
140
|
+
return { valid: false, error: 'codex attestation is not an object' };
|
|
141
|
+
}
|
|
142
|
+
// 统一必填字段
|
|
143
|
+
const requiredStrings = ['platform', 'version', 'planDigest', 'result', 'actor', 'confirmedAt'];
|
|
144
|
+
for (const field of requiredStrings) {
|
|
145
|
+
if (typeof attestation[field] !== 'string' || attestation[field].length === 0) {
|
|
146
|
+
return { valid: false, error: `codex attestation missing required field "${field}"` };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (attestation.platform !== 'codex') {
|
|
150
|
+
return { valid: false, error: `codex attestation platform "${attestation.platform}" must be "codex"` };
|
|
151
|
+
}
|
|
152
|
+
// result 只接受 passed 或 failed
|
|
153
|
+
if (attestation.result !== 'passed' && attestation.result !== 'failed') {
|
|
154
|
+
return { valid: false, error: `codex attestation result "${attestation.result}" must be "passed" or "failed"` };
|
|
155
|
+
}
|
|
156
|
+
// planDigest 绑定验证
|
|
157
|
+
if (!HEX_DIGEST_RE.test(attestation.planDigest)) {
|
|
158
|
+
return { valid: false, error: 'codex attestation planDigest must be a 64-char lowercase hex digest' };
|
|
159
|
+
}
|
|
160
|
+
if (attestation.planDigest !== boundPlanDigest) {
|
|
161
|
+
return { valid: false, error: 'codex attestation planDigest does not match the frozen plan digest' };
|
|
162
|
+
}
|
|
163
|
+
// version 静态一致性检查
|
|
164
|
+
if (attestation.version !== action.version) {
|
|
165
|
+
return { valid: false, error: `codex attestation version "${attestation.version}" does not match action version "${action.version}"` };
|
|
166
|
+
}
|
|
167
|
+
// confirmedAt 必须是有效的时间戳
|
|
168
|
+
const confirmedMs = Date.parse(attestation.confirmedAt);
|
|
169
|
+
if (!Number.isFinite(confirmedMs)) {
|
|
170
|
+
return { valid: false, error: 'codex attestation confirmedAt must be a valid ISO timestamp' };
|
|
171
|
+
}
|
|
172
|
+
// 可选字段 note 如果存在必须是字符串
|
|
173
|
+
if (attestation.note !== undefined && typeof attestation.note !== 'string') {
|
|
174
|
+
return { valid: false, error: 'codex attestation note must be a string when present' };
|
|
175
|
+
}
|
|
176
|
+
return { valid: true, error: null };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Read the authoritative Codex plugin manifest from a verified plugin root.
|
|
181
|
+
*
|
|
182
|
+
* Single candidate `.codex-plugin/plugin.json`. Returns the parsed manifest
|
|
183
|
+
* and the root-relative manifest path. Throws when the manifest is absent,
|
|
184
|
+
* not valid JSON, or not an object.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} pluginRootReal - realpath of the verified plugin root.
|
|
187
|
+
* @returns {Promise<{manifest:object, manifestRelative:string}>}
|
|
188
|
+
*/
|
|
189
|
+
export async function readCodexManifest(pluginRootReal) {
|
|
190
|
+
const manifestRelative = join('.codex-plugin', 'plugin.json');
|
|
191
|
+
const manifestPath = resolve(pluginRootReal, manifestRelative);
|
|
192
|
+
let content;
|
|
193
|
+
try {
|
|
194
|
+
content = await readFile(manifestPath, 'utf8');
|
|
195
|
+
} catch {
|
|
196
|
+
throw new Error(`no codex plugin manifest found (expected ${manifestRelative})`);
|
|
197
|
+
}
|
|
198
|
+
let manifest;
|
|
199
|
+
try {
|
|
200
|
+
manifest = JSON.parse(content);
|
|
201
|
+
} catch {
|
|
202
|
+
throw new Error(`codex plugin manifest ${manifestRelative} is not valid JSON`);
|
|
203
|
+
}
|
|
204
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
205
|
+
throw new Error(`codex plugin manifest ${manifestRelative} is not an object`);
|
|
206
|
+
}
|
|
207
|
+
return { manifest, manifestRelative };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Codex human-attestation fallback: when the CLI interface, environment, or
|
|
212
|
+
* transport is unavailable, emit a manual-install requirement bound to the
|
|
213
|
+
* real frozen plan digest + identity. Explicit mismatches (identity, version,
|
|
214
|
+
* payload, marketplace entry) are hard failures and never reach this path.
|
|
215
|
+
*
|
|
216
|
+
* @param {object} action - expanded codex action (validated params already).
|
|
217
|
+
* @param {object} context - adapter context (root, runDir, plan).
|
|
218
|
+
* @returns {Promise<import('../adapters/contract.mjs').AdapterResult>}
|
|
219
|
+
*/
|
|
220
|
+
export async function executeCodexManualRequirement(action, context) {
|
|
221
|
+
const actionType = ActionType.CODEX_MARKETPLACE_INSTALL;
|
|
222
|
+
|
|
223
|
+
// Bind to the REAL frozen plan digest via strict normalized recompute.
|
|
224
|
+
let planDigest;
|
|
225
|
+
try {
|
|
226
|
+
planDigest = await resolveCodexBoundPlanDigest(context);
|
|
227
|
+
} catch (planErr) {
|
|
228
|
+
return createResult({
|
|
229
|
+
actionType,
|
|
230
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
231
|
+
error: `cannot bind codex requirement to the frozen plan: ${planErr.message}`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Validate the frozen timeout.
|
|
236
|
+
try {
|
|
237
|
+
resolveTimeoutMs(action);
|
|
238
|
+
} catch (timeoutErr) {
|
|
239
|
+
return createResult({
|
|
240
|
+
actionType,
|
|
241
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
242
|
+
error: timeoutErr.message,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const ref = action.ref ?? `v${action.version}`;
|
|
247
|
+
|
|
248
|
+
// Stable, plan-digest-keyed authority dir.
|
|
249
|
+
let attestationDir;
|
|
250
|
+
try {
|
|
251
|
+
attestationDir = codexAuthorityDir(context, planDigest, action.plugin);
|
|
252
|
+
} catch (dirErr) {
|
|
253
|
+
return createResult({
|
|
254
|
+
actionType,
|
|
255
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
256
|
+
error: dirErr.message,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const instructions = [
|
|
261
|
+
`Codex CLI 不可用(接口、环境或传输问题),降级为人工结果路径。`,
|
|
262
|
+
`1) publish 完成所有远端写入后进入 PUBLISHED 状态(自动化 Git 分支/标签、npm 和 GitHub Release 写入已完成)。此 codex 检查点标记为需要人工安装。`,
|
|
263
|
+
`2) 手动安装插件 "${action.plugin}" 版本 ${action.version}(ref: ${ref})。`,
|
|
264
|
+
`3) 将人工结果 JSON 写入: ${attestationDir}/${CODEX_ATTESTATION_FILE}`,
|
|
265
|
+
` 必填字段: platform="codex", version, planDigest(冻结计划摘要), result("passed" 或 "failed"), actor(确认人), confirmedAt(ISO 8601 时间戳)`,
|
|
266
|
+
` 可选字段: note(备注)`,
|
|
267
|
+
`4) 运行 release-skill verify(从同一个计划摘要索引的权威目录读取结果,成功后 -> VERIFIED)。`,
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
// 统一 requirement 结构
|
|
271
|
+
const requirement = {
|
|
272
|
+
kind: 'codex-manual-install-requirement',
|
|
273
|
+
platform: 'codex',
|
|
274
|
+
plugin: action.plugin,
|
|
275
|
+
version: action.version,
|
|
276
|
+
repo: action.repo,
|
|
277
|
+
ref,
|
|
278
|
+
entrySkill: action.entrySkill,
|
|
279
|
+
planDigest,
|
|
280
|
+
attestationDir,
|
|
281
|
+
attestationFile: CODEX_ATTESTATION_FILE,
|
|
282
|
+
attestationTemplate: {
|
|
283
|
+
platform: 'codex',
|
|
284
|
+
version: action.version,
|
|
285
|
+
planDigest,
|
|
286
|
+
result: '<"passed" or "failed">',
|
|
287
|
+
actor: '<person who confirmed the install>',
|
|
288
|
+
confirmedAt: '<ISO 8601 timestamp>',
|
|
289
|
+
note: '<optional note>',
|
|
290
|
+
},
|
|
291
|
+
instructions,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Ensure the authority directory exists.
|
|
295
|
+
const { mkdir } = await import('node:fs/promises');
|
|
296
|
+
try {
|
|
297
|
+
await mkdir(attestationDir, { recursive: true, mode: 0o700 });
|
|
298
|
+
} catch (mkdirErr) {
|
|
299
|
+
if (mkdirErr?.code !== 'EEXIST') {
|
|
300
|
+
return createResult({
|
|
301
|
+
actionType,
|
|
302
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
303
|
+
error: `cannot create codex attestation directory: ${mkdirErr.message}`,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Idempotent requirement write.
|
|
309
|
+
const requirementPath = resolve(attestationDir, CODEX_REQUIREMENT_FILE);
|
|
310
|
+
let existing = null;
|
|
311
|
+
let requirementMissing = false;
|
|
312
|
+
try {
|
|
313
|
+
const existingRaw = await readFile(requirementPath, 'utf8');
|
|
314
|
+
try {
|
|
315
|
+
existing = JSON.parse(existingRaw);
|
|
316
|
+
} catch (parseErr) {
|
|
317
|
+
return createResult({
|
|
318
|
+
actionType,
|
|
319
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
320
|
+
error: `existing codex manual-install requirement is invalid JSON; refusing to overwrite: ${parseErr.message}`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
} catch (readErr) {
|
|
324
|
+
if (readErr?.code === 'ENOENT') {
|
|
325
|
+
requirementMissing = true;
|
|
326
|
+
} else {
|
|
327
|
+
return createResult({
|
|
328
|
+
actionType,
|
|
329
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
330
|
+
error: `existing codex manual-install requirement cannot be read; refusing to overwrite: ${readErr.message}`,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!requirementMissing) {
|
|
335
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
336
|
+
return createResult({
|
|
337
|
+
actionType,
|
|
338
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
339
|
+
error: 'existing codex manual-install requirement is not an object; refusing to overwrite',
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const { createdAt: _existingCreatedAt, ...existingBody } = existing;
|
|
343
|
+
if (canonicalJson(existingBody) !== canonicalJson(requirement)) {
|
|
344
|
+
return createResult({
|
|
345
|
+
actionType,
|
|
346
|
+
status: ActionStatus.EXECUTE_FAILED,
|
|
347
|
+
error: 'existing codex manual-install requirement conflicts with the current frozen action; refusing to overwrite',
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
await writeEvidenceAtomic(requirementPath, { ...requirement, createdAt: new Date().toISOString() });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return createResult({
|
|
355
|
+
actionType,
|
|
356
|
+
status: ActionStatus.EXECUTED,
|
|
357
|
+
observation: {
|
|
358
|
+
installed: false,
|
|
359
|
+
manualInstallRequired: true,
|
|
360
|
+
platform: 'codex',
|
|
361
|
+
plugin: action.plugin,
|
|
362
|
+
version: action.version,
|
|
363
|
+
ref,
|
|
364
|
+
planDigest,
|
|
365
|
+
attestationDir,
|
|
366
|
+
instructions,
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
}
|