release-skill 0.8.0 → 0.9.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/.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 +65 -7
- package/INSTALL.md +14 -2
- package/INSTALL.zh-CN.md +12 -2
- package/README.md +51 -31
- package/README.zh-CN.md +33 -20
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/error-codes.json +1 -1
- package/adapters/claude/bin/foundation-resource-binding.json +1 -1
- package/adapters/claude/bin/prebuild-manifest.json +8 -4
- package/adapters/claude/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/claude/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/claude/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/claude/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/claude/bin/registry.json +22 -1
- package/adapters/claude/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/claude/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/claude/bin/rules.json +1 -1
- package/adapters/claude/schemas/release-plan.schema.json +43 -5
- package/adapters/claude/schemas/release-run.schema.json +35 -1
- package/adapters/claude/skills/release-finish/SKILL.md +65 -0
- package/adapters/claude/skills/release-verify/SKILL.md +6 -1
- package/adapters/claude/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/claude/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/claude/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/claude/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/claude/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/error-codes.json +1 -1
- package/adapters/codex/bin/foundation-resource-binding.json +1 -1
- package/adapters/codex/bin/prebuild-manifest.json +8 -4
- package/adapters/codex/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/codex/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/codex/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/codex/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/codex/bin/registry.json +22 -1
- package/adapters/codex/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/codex/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/codex/bin/rules.json +1 -1
- package/adapters/codex/schemas/release-plan.schema.json +43 -5
- package/adapters/codex/schemas/release-run.schema.json +35 -1
- package/adapters/codex/skills/release-finish/SKILL.md +72 -0
- package/adapters/codex/skills/release-verify/SKILL.md +6 -1
- package/adapters/codex/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/codex/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/codex/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/codex/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/codex/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/error-codes.json +1 -1
- package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
- package/adapters/kimi/bin/prebuild-manifest.json +8 -4
- package/adapters/kimi/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/kimi/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/kimi/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/kimi/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/kimi/bin/registry.json +22 -1
- package/adapters/kimi/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/kimi/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/kimi/bin/rules.json +1 -1
- package/adapters/kimi/schemas/release-plan.schema.json +43 -5
- package/adapters/kimi/schemas/release-run.schema.json +35 -1
- package/adapters/kimi/skills/release-finish/SKILL.md +72 -0
- package/adapters/kimi/skills/release-verify/SKILL.md +6 -1
- package/adapters/kimi/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/kimi/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/kimi/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/kimi/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/kimi/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/error-codes.json +1 -1
- package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
- package/adapters/workbuddy/bin/prebuild-manifest.json +8 -4
- package/adapters/workbuddy/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/adapters/workbuddy/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/adapters/workbuddy/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/adapters/workbuddy/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/adapters/workbuddy/bin/registry.json +22 -1
- package/adapters/workbuddy/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +7152 -4231
- package/adapters/workbuddy/bin/rules.json +1 -1
- package/adapters/workbuddy/schemas/release-plan.schema.json +43 -5
- package/adapters/workbuddy/schemas/release-run.schema.json +35 -1
- package/adapters/workbuddy/skills/release-finish/SKILL.md +65 -0
- package/adapters/workbuddy/skills/release-verify/SKILL.md +6 -1
- package/adapters/workbuddy/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/adapters/workbuddy/src/schemas/host-verification-result.schema.json +2 -2
- package/adapters/workbuddy/src/schemas/plugin-verification-request.schema.json +374 -0
- package/adapters/workbuddy/src/schemas/plugin-verification-result.schema.json +799 -0
- package/adapters/workbuddy/src/schemas/watchdog-termination-envelope.schema.json +16 -0
- package/bin/error-codes.json +1 -1
- package/bin/foundation-resource-binding.json +1 -1
- package/bin/prebuild-manifest.json +8 -4
- package/bin/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/bin/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/bin/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/bin/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/bin/registry.json +22 -1
- package/bin/release-skill-cli.mjs +126 -1
- package/bin/release-skill-local-finish.mjs +4 -0
- package/bin/release-skill.bundle.mjs +7152 -4231
- package/bin/rules.json +1 -1
- package/package.json +4 -4
- package/platform-manifest.json +30 -4
- package/schemas/release-plan.schema.json +43 -5
- package/schemas/release-run.schema.json +35 -1
- package/skills/release-finish/SKILL.md +65 -0
- package/skills/release-verify/SKILL.md +6 -1
- package/skills-src/release-finish/SKILL.md +65 -0
- package/skills-src/release-verify/SKILL.md +6 -1
- package/src/commands/approve.mjs +14 -2
- package/src/commands/distribute.mjs +562 -321
- package/src/commands/post-release-local.mjs +611 -0
- package/src/commands/postverify.mjs +262 -155
- package/src/commands/prepare.mjs +83 -57
- package/src/commands/publish.mjs +11 -10
- package/src/commands/reconcile.mjs +11 -10
- package/src/commands/setup.mjs +26 -13
- package/src/commands/ship.mjs +45 -12
- package/src/commands/verify.mjs +81 -18
- package/src/core/adoption-assessment.mjs +0 -3
- package/src/core/approval.mjs +11 -8
- package/src/core/foundation-plugin-verification.mjs +247 -0
- package/src/core/hooks.mjs +0 -16
- package/src/core/plan.mjs +19 -11
- package/src/core/postpublish-approval.mjs +12 -2
- package/src/core/postpublish-bundle.mjs +51 -13
- package/src/core/postpublish.mjs +146 -6
- package/src/core/preset-executor.mjs +53 -0
- package/src/core/proposal-inbox.mjs +91 -27
- package/src/core/recovery.mjs +27 -12
- package/src/core/skill-resource-closure.mjs +8 -4
- package/src/producers/build-adapters.mjs +10 -1
- package/src/producers/foundation-resource-projection.mjs +3 -3
- package/src/schemas/filesystem-tree-observation.schema.json +55 -0
- package/src/schemas/host-verification-result.schema.json +2 -2
- package/src/schemas/plugin-verification-request.schema.json +374 -0
- package/src/schemas/plugin-verification-result.schema.json +799 -0
- package/src/schemas/watchdog-termination-envelope.schema.json +16 -0
|
@@ -58,13 +58,16 @@ import { boundedOutputTail } from '../core/bounded-output.mjs';
|
|
|
58
58
|
import {
|
|
59
59
|
buildPostPublishContext,
|
|
60
60
|
validatePostPublishDeclaration,
|
|
61
|
+
normalizePostPublishView,
|
|
62
|
+
postPublishActionId,
|
|
61
63
|
effectiveHookRequiresApproval,
|
|
64
|
+
validatePostPublishHookIdUniqueness,
|
|
62
65
|
POSTPUBLISH_CONTEXT_ENV,
|
|
63
66
|
PAYLOAD_SOURCE_TAG_WORKTREE,
|
|
64
67
|
} from '../core/postpublish.mjs';
|
|
65
|
-
import { verifyAndInstallExecutionBundle } from '../core/postpublish-bundle.mjs';
|
|
68
|
+
import { verifyAndInstallExecutionBundle, verifyExecutionBundle } from '../core/postpublish-bundle.mjs';
|
|
66
69
|
import { assertPostPublishApprovalAuthority, validatePostPublishApproval } from '../core/postpublish-approval.mjs';
|
|
67
|
-
import { executePresetHook } from '../core/preset-executor.mjs';
|
|
70
|
+
import { executePresetHook, preflightPresetHook } from '../core/preset-executor.mjs';
|
|
68
71
|
import {
|
|
69
72
|
ReleaseError,
|
|
70
73
|
GATE_FAILED,
|
|
@@ -185,13 +188,28 @@ export async function postVerifyRelease(options) {
|
|
|
185
188
|
{ expected: plan.digest, actual: actualDigest },
|
|
186
189
|
);
|
|
187
190
|
}
|
|
188
|
-
|
|
191
|
+
// v3 contract (§4.3): postPublish is a frozen declaration ARRAY. All reads
|
|
192
|
+
// go through the single read-only normalization entry normalizePostPublishView.
|
|
193
|
+
// Legacy (planVersion 1/2) absent stays fail-closed; a v3 empty array means
|
|
194
|
+
// "no post-release execution" and completes directly with zero hooks.
|
|
195
|
+
const declarations = normalizePostPublishView(plan);
|
|
196
|
+
// Rework R-02: the single array-level explicit-hook-id uniqueness authority
|
|
197
|
+
// (core/postpublish.mjs) re-asserts the frozen view here — a digest-correct,
|
|
198
|
+
// schema-valid plan with duplicate hook ids across units must fail before
|
|
199
|
+
// any approval consumption or hook execution.
|
|
200
|
+
validatePostPublishHookIdUniqueness(declarations);
|
|
201
|
+
if (declarations.length === 0 && plan.planVersion !== 3) {
|
|
189
202
|
throw new ReleaseError(
|
|
190
203
|
GATE_FAILED,
|
|
191
204
|
'release plan has no postPublish declaration; nothing to postVerify',
|
|
192
205
|
{ planPath },
|
|
193
206
|
);
|
|
194
207
|
}
|
|
208
|
+
if (declarations.length === 0) {
|
|
209
|
+
// v3 zero declarations (§6 matrix row 0): the post-release phase is
|
|
210
|
+
// skipped — no hook, no external write.
|
|
211
|
+
return { planPath, runPath: null, status: DISTRIBUTED, checkpoints: [] };
|
|
212
|
+
}
|
|
195
213
|
|
|
196
214
|
// =========================================================================
|
|
197
215
|
// Gate 2: approval record validation (binding checks; the post-publish
|
|
@@ -278,12 +296,29 @@ export async function postVerifyRelease(options) {
|
|
|
278
296
|
validateRunCheckpointMapping(publishRun, plan.externalActions ?? []);
|
|
279
297
|
|
|
280
298
|
// =========================================================================
|
|
281
|
-
// Declaration re-validation + postVerify-phase hook selection
|
|
299
|
+
// Declaration re-validation + postVerify-phase hook selection (v3 contract
|
|
300
|
+
// §4.3): EVERY declaration is validated before any write. Legacy plans
|
|
301
|
+
// present exactly one declaration (normalizePostPublishView) and keep their
|
|
302
|
+
// exact behavior.
|
|
282
303
|
// =========================================================================
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
304
|
+
const v3 = plan.planVersion === 3;
|
|
305
|
+
// Checkpoint action ids are unit-namespaced on v3; legacy ids are unchanged
|
|
306
|
+
// (existing runs, tests and approvals pin the bare local ids). Shared
|
|
307
|
+
// derivation core/postpublish.mjs `postPublishActionId`.
|
|
308
|
+
const unitActionId = (unitId, localId) => postPublishActionId({ planVersion: plan.planVersion, unitId, localId });
|
|
309
|
+
|
|
310
|
+
const prepared = [];
|
|
311
|
+
for (const declaration of declarations) {
|
|
312
|
+
validatePostPublishDeclaration(declaration, { unitId: declaration.unitId });
|
|
313
|
+
// R-01 (rework): the frozen execution bundle closure is re-verified for
|
|
314
|
+
// EVERY declaration BEFORE any hook runs — a later declaration's
|
|
315
|
+
// missing/drifted bundle must fail the whole run before unit A executes.
|
|
316
|
+
await verifyExecutionBundle({ planPath, postPublish: declaration });
|
|
317
|
+
const declaredHooks = declaration.hooks ?? [];
|
|
318
|
+
const postVerifyHooks = declaredHooks.filter((hook) => (hook.phase ?? 'distribute') === 'postVerify');
|
|
319
|
+
prepared.push({ declaration, postVerifyHooks });
|
|
320
|
+
}
|
|
321
|
+
const postVerifyHooks = prepared.flatMap((prep) => prep.postVerifyHooks);
|
|
287
322
|
|
|
288
323
|
// =========================================================================
|
|
289
324
|
// Gate 4: checkpoint approvals for requiresApproval hooks. Every provided
|
|
@@ -341,50 +376,55 @@ export async function postVerifyRelease(options) {
|
|
|
341
376
|
// authority; the live local tag is diagnostic only. Read-only observation,
|
|
342
377
|
// still before any write.
|
|
343
378
|
// =========================================================================
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
tagAuthority
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
379
|
+
for (const prep of prepared) {
|
|
380
|
+
const { declaration } = prep;
|
|
381
|
+
if (declaration.payloadSource !== PAYLOAD_SOURCE_TAG_WORKTREE) {
|
|
382
|
+
throw new ReleaseError(
|
|
383
|
+
GATE_FAILED,
|
|
384
|
+
`postPublish.payloadSource must be "${PAYLOAD_SOURCE_TAG_WORKTREE}"`,
|
|
385
|
+
{ payloadSource: declaration.payloadSource, unitId: declaration.unitId },
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (typeof declaration.tagCommit !== 'string' || !SHA_RE.test(declaration.tagCommit)) {
|
|
389
|
+
throw new ReleaseError(
|
|
390
|
+
GATE_FAILED,
|
|
391
|
+
'postPublish is missing a frozen tagCommit binding; postVerify fails closed',
|
|
392
|
+
{ tagCommit: declaration.tagCommit ?? null, unitId: declaration.unitId },
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
let tagAuthority;
|
|
396
|
+
try {
|
|
397
|
+
tagAuthority = await resolveFrozenTagAuthority({
|
|
398
|
+
plan,
|
|
399
|
+
unitId: declaration.unitId,
|
|
400
|
+
// T04 (裁决 14): the tag proof is taken from the legitimate PUBLISHED
|
|
401
|
+
// parent run — never from the verify receipt.
|
|
402
|
+
sourceRun: publishRun,
|
|
403
|
+
root,
|
|
404
|
+
exec,
|
|
405
|
+
observeTagFn,
|
|
406
|
+
observeBranchFn,
|
|
407
|
+
});
|
|
408
|
+
} catch (err) {
|
|
409
|
+
throw err instanceof ReleaseError ? err : new ReleaseError(
|
|
410
|
+
GATE_FAILED,
|
|
411
|
+
`tag identity cannot be verified: ${err?.message ?? err}`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (tagAuthority.tag !== declaration.tag || tagAuthority.commit !== declaration.tagCommit) {
|
|
415
|
+
throw new ReleaseError(
|
|
416
|
+
GATE_FAILED,
|
|
417
|
+
'frozen postPublish tag identity conflicts with the frozen create-tag binding',
|
|
418
|
+
{
|
|
419
|
+
postPublishTag: declaration.tag,
|
|
420
|
+
postPublishTagCommit: declaration.tagCommit,
|
|
421
|
+
bindingTag: tagAuthority.tag,
|
|
422
|
+
bindingCommit: tagAuthority.commit,
|
|
423
|
+
unitId: declaration.unitId,
|
|
424
|
+
},
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
prep.tagAuthority = tagAuthority;
|
|
388
428
|
}
|
|
389
429
|
|
|
390
430
|
// =========================================================================
|
|
@@ -405,12 +445,12 @@ export async function postVerifyRelease(options) {
|
|
|
405
445
|
const startedAt = clockFn();
|
|
406
446
|
let stateSequence = -1;
|
|
407
447
|
|
|
408
|
-
const checkpoints = postVerifyHooks.map((hook) => ({
|
|
409
|
-
actionId: hook.id,
|
|
448
|
+
const checkpoints = prepared.flatMap((prep) => prep.postVerifyHooks.map((hook) => ({
|
|
449
|
+
actionId: unitActionId(prep.declaration.unitId, hook.id),
|
|
410
450
|
actionType: 'postpublish-hook',
|
|
411
451
|
status: 'PENDING',
|
|
412
452
|
executor: EXECUTOR,
|
|
413
|
-
}));
|
|
453
|
+
})));
|
|
414
454
|
const checkpointById = new Map(checkpoints.map((cp) => [cp.actionId, cp]));
|
|
415
455
|
|
|
416
456
|
const buildPersistedState = (status, finishedAt) => ({
|
|
@@ -450,11 +490,20 @@ export async function postVerifyRelease(options) {
|
|
|
450
490
|
return appendRunState(runDir, stateSequence, buildPersistedState(status));
|
|
451
491
|
};
|
|
452
492
|
|
|
453
|
-
|
|
454
|
-
|
|
493
|
+
// External-success facts (declared before recordFailure: an exception after
|
|
494
|
+
// a succeeded hook must persist PARTIAL, never BLOCKED — rework R-01).
|
|
495
|
+
let externalSuccesses = 0;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Persist the failure run record (idempotent) once the run exists.
|
|
499
|
+
* Rework R-01: PARTIAL when an external checkpoint already succeeded (the
|
|
500
|
+
* success checkpoint is retained), BLOCKED at zero side effects.
|
|
501
|
+
*/
|
|
502
|
+
const recordFailure = async () => {
|
|
455
503
|
if (finalRecordWritten) return;
|
|
504
|
+
const status = externalSuccesses > 0 ? PARTIAL : BLOCKED;
|
|
456
505
|
try {
|
|
457
|
-
await writeRunAtomic(runPath, buildPersistedState(
|
|
506
|
+
await writeRunAtomic(runPath, buildPersistedState(status, clockFn()));
|
|
458
507
|
finalRecordWritten = true;
|
|
459
508
|
} catch {
|
|
460
509
|
// Persistence must never mask the primary failure.
|
|
@@ -488,6 +537,52 @@ export async function postVerifyRelease(options) {
|
|
|
488
537
|
// Durable pre-execute authority (seq 0).
|
|
489
538
|
await snapshot(DISTRIBUTING);
|
|
490
539
|
|
|
540
|
+
// =======================================================================
|
|
541
|
+
// R-01 (rework): preset deterministic-conflict preflight — the narrow
|
|
542
|
+
// READ-ONLY entry (core/preset-executor.mjs preflightPresetHook) calls
|
|
543
|
+
// the SAME observation implementation the execution-phase delivery uses,
|
|
544
|
+
// for every postVerify hook that will actually execute this run. A
|
|
545
|
+
// deterministic conflict (e.g. the proposal path already exists with
|
|
546
|
+
// different bytes) fails the whole run here, BEFORE the first hook. This
|
|
547
|
+
// is not a lock: the execution phase re-observes every remote regardless.
|
|
548
|
+
// Hooks parked at AWAITING_APPROVAL are not observed here — the approved
|
|
549
|
+
// rerun observes them at execution time.
|
|
550
|
+
// =======================================================================
|
|
551
|
+
for (const prep of prepared) {
|
|
552
|
+
const { declaration, postVerifyHooks: preflightHooks } = prep;
|
|
553
|
+
for (const hook of preflightHooks) {
|
|
554
|
+
if (effectiveHookRequiresApproval(hook) && !approvedHookIds.has(hook.id)) continue;
|
|
555
|
+
const proposalProjection = buildPostPublishContext({
|
|
556
|
+
plan,
|
|
557
|
+
postPublish: declaration,
|
|
558
|
+
runId: `postverify-${verifyRun.runId}`,
|
|
559
|
+
sourceRun: publishRun,
|
|
560
|
+
payloadDir: undefined,
|
|
561
|
+
phase: 'postVerify',
|
|
562
|
+
verifyEvidence: {
|
|
563
|
+
runId: verifyRun.runId,
|
|
564
|
+
status: verifyRun.status,
|
|
565
|
+
finishedAt: verifyRun.finishedAt,
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
const observation = await preflightPresetHook({
|
|
569
|
+
hook,
|
|
570
|
+
contextProjection: proposalProjection,
|
|
571
|
+
exec,
|
|
572
|
+
});
|
|
573
|
+
if (observation !== null) {
|
|
574
|
+
await evidence.append({
|
|
575
|
+
phase: 'safety-gate',
|
|
576
|
+
gate: 'preset-preflight',
|
|
577
|
+
status: 'passed',
|
|
578
|
+
hookId: hook.id,
|
|
579
|
+
unitId: declaration.unitId,
|
|
580
|
+
verdict: observation.verdict,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
491
586
|
// =======================================================================
|
|
492
587
|
// F-04 root split: postVerify holds TWO distinct roots —
|
|
493
588
|
// - releaseWorkspaceRoot: the real project root the user releases from;
|
|
@@ -502,7 +597,7 @@ export async function postVerifyRelease(options) {
|
|
|
502
597
|
try {
|
|
503
598
|
releaseWorkspaceRoot = await realpath(root);
|
|
504
599
|
} catch (err) {
|
|
505
|
-
await
|
|
600
|
+
await recordFailure();
|
|
506
601
|
throw new ReleaseError(
|
|
507
602
|
GATE_FAILED,
|
|
508
603
|
`release workspace root does not resolve to an existing directory: ${err.message}`,
|
|
@@ -510,107 +605,113 @@ export async function postVerifyRelease(options) {
|
|
|
510
605
|
);
|
|
511
606
|
}
|
|
512
607
|
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
608
|
+
// Saga-level chain state (single run authority; the source VERIFIED
|
|
609
|
+
// verify run is never touched — a partial postVerify never downgrades it).
|
|
610
|
+
let hooksStopped = false;
|
|
611
|
+
let failures = 0;
|
|
612
|
+
let awaitingApproval = 0;
|
|
613
|
+
|
|
614
|
+
// =========================================================================
|
|
615
|
+
// postVerify hooks, per declaration in frozen order. A failure stops the
|
|
616
|
+
// chain; a requiresApproval hook without a checkpoint approval parks at
|
|
617
|
+
// AWAITING_APPROVAL and never executes; dry-run executes nothing.
|
|
618
|
+
// =========================================================================
|
|
619
|
+
for (const prep of prepared) {
|
|
620
|
+
const { declaration, postVerifyHooks: declarationHooks } = prep;
|
|
621
|
+
const tagAuthority = prep.tagAuthority;
|
|
622
|
+
|
|
623
|
+
// R1 timing contract carries over: hooks run inside a detached worktree
|
|
624
|
+
// at the frozen tagCommit, never in the live workspace. R-01: the
|
|
625
|
+
// worktree is materialized from a COPY of the frozen bare repo (never
|
|
626
|
+
// registered in the source repo's worktree metadata). Dry-run executes
|
|
627
|
+
// nothing, so no worktree is allocated for a rehearsal.
|
|
628
|
+
if (dryRun !== true) {
|
|
629
|
+
try {
|
|
630
|
+
tmpBase = await mkdtemp(join(tmpdir(), 'release-skill-postverify-'));
|
|
631
|
+
({ worktreePath } = await createFrozenTagWorktree({
|
|
632
|
+
gitDir: tagAuthority.gitDir,
|
|
633
|
+
commit: tagAuthority.commit,
|
|
634
|
+
tmpBase,
|
|
635
|
+
exec,
|
|
636
|
+
}));
|
|
637
|
+
} catch (err) {
|
|
638
|
+
await evidence.append({
|
|
639
|
+
phase: 'worktree',
|
|
640
|
+
status: 'failed',
|
|
641
|
+
error: asError('WORKTREE_FAILED', boundedOutputTail(err?.stderr ?? err?.message)),
|
|
642
|
+
});
|
|
643
|
+
await recordFailure();
|
|
644
|
+
throw new ReleaseError(
|
|
645
|
+
GATE_FAILED,
|
|
646
|
+
`cannot create the detached tag worktree at ${declaration.tagCommit}: ${err?.message ?? err}`,
|
|
647
|
+
{ tagCommit: declaration.tagCommit },
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
await evidence.append({ phase: 'worktree', status: 'passed' });
|
|
651
|
+
|
|
652
|
+
// ===================================================================
|
|
653
|
+
// Private execution bundle (F-01 / T1): postVerify re-entry consumes
|
|
654
|
+
// the SAME frozen bundle bytes — never the live workspace copies.
|
|
655
|
+
// Strictly re-read the digest-addressed bytes, recompute the closure
|
|
656
|
+
// through Foundation, and install ONLY the verified bytes into the
|
|
657
|
+
// fresh tag worktree before any hook runs; any mismatch fails closed
|
|
658
|
+
// before a hook or an external write.
|
|
659
|
+
// ===================================================================
|
|
660
|
+
let installedBundlePaths = [];
|
|
661
|
+
try {
|
|
662
|
+
({ installed: installedBundlePaths } = await verifyAndInstallExecutionBundle({
|
|
663
|
+
planPath,
|
|
664
|
+
worktreePath,
|
|
665
|
+
postPublish: declaration,
|
|
666
|
+
}));
|
|
667
|
+
} catch (err) {
|
|
668
|
+
await evidence.append({
|
|
669
|
+
phase: 'worktree',
|
|
670
|
+
gate: 'execution-bundle',
|
|
671
|
+
status: 'failed',
|
|
672
|
+
error: asError('EXECUTION_BUNDLE_FAILED', boundedOutputTail(err?.message ?? String(err))),
|
|
673
|
+
});
|
|
674
|
+
await recordFailure();
|
|
675
|
+
throw err instanceof ReleaseError ? err : new ReleaseError(
|
|
676
|
+
GATE_FAILED,
|
|
677
|
+
`cannot verify the frozen execution bundle: ${err?.message ?? err}`,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
558
680
|
await evidence.append({
|
|
559
681
|
phase: 'worktree',
|
|
560
682
|
gate: 'execution-bundle',
|
|
561
|
-
status: '
|
|
562
|
-
|
|
683
|
+
status: 'passed',
|
|
684
|
+
installed: installedBundlePaths,
|
|
563
685
|
});
|
|
564
|
-
await recordBlocked();
|
|
565
|
-
throw err instanceof ReleaseError ? err : new ReleaseError(
|
|
566
|
-
GATE_FAILED,
|
|
567
|
-
`cannot verify the frozen execution bundle: ${err?.message ?? err}`,
|
|
568
|
-
);
|
|
569
686
|
}
|
|
570
|
-
await evidence.append({
|
|
571
|
-
phase: 'worktree',
|
|
572
|
-
gate: 'execution-bundle',
|
|
573
|
-
status: 'passed',
|
|
574
|
-
installed: installedBundlePaths,
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// §2.3 context projection: verifyEvidence PRESENT (postVerify phase);
|
|
579
|
-
// publishedAt from the sealed publish run; payloadDir never travels.
|
|
580
|
-
const verifyEvidence = {
|
|
581
|
-
runId: verifyRun.runId,
|
|
582
|
-
status: verifyRun.status,
|
|
583
|
-
finishedAt: verifyRun.finishedAt,
|
|
584
|
-
};
|
|
585
|
-
const hookContextProjection = buildPostPublishContext({
|
|
586
|
-
plan,
|
|
587
|
-
runId,
|
|
588
|
-
sourceRun: publishRun,
|
|
589
|
-
payloadDir: undefined,
|
|
590
|
-
phase: 'postVerify',
|
|
591
|
-
verifyEvidence,
|
|
592
|
-
});
|
|
593
687
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
688
|
+
// §2.3 context projection: verifyEvidence PRESENT (postVerify phase);
|
|
689
|
+
// publishedAt from the sealed publish run; payloadDir never travels.
|
|
690
|
+
const verifyEvidence = {
|
|
691
|
+
runId: verifyRun.runId,
|
|
692
|
+
status: verifyRun.status,
|
|
693
|
+
finishedAt: verifyRun.finishedAt,
|
|
694
|
+
};
|
|
695
|
+
const hookContextProjection = buildPostPublishContext({
|
|
696
|
+
plan,
|
|
697
|
+
postPublish: declaration,
|
|
698
|
+
runId,
|
|
699
|
+
sourceRun: publishRun,
|
|
700
|
+
payloadDir: undefined,
|
|
701
|
+
phase: 'postVerify',
|
|
702
|
+
verifyEvidence,
|
|
703
|
+
});
|
|
601
704
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
let awaitingApproval = 0;
|
|
610
|
-
let externalSuccesses = 0;
|
|
705
|
+
// Proposal documents must stay byte-deterministic across redeliveries of
|
|
706
|
+
// the SAME release event (NO_CHANGE idempotence): they travel with the
|
|
707
|
+
// stable lineage-derived event identity, not the per-attempt runId.
|
|
708
|
+
const proposalContextProjection = {
|
|
709
|
+
...hookContextProjection,
|
|
710
|
+
runId: `postverify-${verifyRun.runId}`,
|
|
711
|
+
};
|
|
611
712
|
|
|
612
|
-
|
|
613
|
-
|
|
713
|
+
for (const hook of declarationHooks) {
|
|
714
|
+
const cp = checkpointById.get(unitActionId(declaration.unitId, hook.id));
|
|
614
715
|
cp.startedAt = clockFn();
|
|
615
716
|
|
|
616
717
|
if (hooksStopped) {
|
|
@@ -657,7 +758,7 @@ export async function postVerifyRelease(options) {
|
|
|
657
758
|
hook,
|
|
658
759
|
contextProjection: hookContextProjection,
|
|
659
760
|
proposalContextProjection,
|
|
660
|
-
commitIdentity:
|
|
761
|
+
commitIdentity: declaration.commitIdentity,
|
|
661
762
|
// F-04: presets receive the RELEASE workspace root (target.workspace
|
|
662
763
|
// resolution + release-workspace write exclusion). The detached
|
|
663
764
|
// worktree is the execution worktree and never impersonates it.
|
|
@@ -743,6 +844,7 @@ export async function postVerifyRelease(options) {
|
|
|
743
844
|
try {
|
|
744
845
|
hookExecution = await hookRunner(
|
|
745
846
|
{
|
|
847
|
+
id: hook.id,
|
|
746
848
|
command: hook.command,
|
|
747
849
|
...(hook.cwd ? { cwd: hook.cwd } : {}),
|
|
748
850
|
...(hook.timeoutMs !== undefined ? { timeoutMs: hook.timeoutMs } : {}),
|
|
@@ -802,6 +904,11 @@ export async function postVerifyRelease(options) {
|
|
|
802
904
|
await snapshot(PARTIAL);
|
|
803
905
|
}
|
|
804
906
|
|
|
907
|
+
// This declaration's scratch worktree is consumed; release it. The
|
|
908
|
+
// outer finally keeps the safety net for a mid-declaration throw.
|
|
909
|
+
await cleanupWorktree();
|
|
910
|
+
}
|
|
911
|
+
|
|
805
912
|
// =========================================================================
|
|
806
913
|
// Classification (returned, not thrown) — distribute saga family:
|
|
807
914
|
// - DISTRIBUTED: no failures and no awaiting-approval hooks;
|
|
@@ -846,12 +953,12 @@ export async function postVerifyRelease(options) {
|
|
|
846
953
|
status: 'failed',
|
|
847
954
|
error: { code: err.code, message: err.message },
|
|
848
955
|
});
|
|
849
|
-
if (!finalRecordWritten) await
|
|
956
|
+
if (!finalRecordWritten) await recordFailure();
|
|
850
957
|
} catch {
|
|
851
958
|
// Persistence must never mask the primary failure.
|
|
852
959
|
}
|
|
853
960
|
await evidence.finish({
|
|
854
|
-
status: BLOCKED,
|
|
961
|
+
status: externalSuccesses > 0 ? PARTIAL : BLOCKED,
|
|
855
962
|
error: { code: err.code, message: err.message },
|
|
856
963
|
failedAt: clockFn(),
|
|
857
964
|
}).catch(() => {});
|