@allocator-one/rcl 4.5.3 → 4.5.5
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 -3
- package/dist/converge/attempt-budget.d.ts +14 -0
- package/dist/converge/attempt-budget.js +216 -1
- package/dist/converge/attempt-budget.js.map +1 -1
- package/dist/converge/historical-delivery-reconciliation-schema.d.ts +131 -0
- package/dist/converge/historical-delivery-reconciliation-schema.js +111 -0
- package/dist/converge/historical-delivery-reconciliation-schema.js.map +1 -0
- package/dist/converge/historical-delivery-reconciliation.d.ts +15 -0
- package/dist/converge/historical-delivery-reconciliation.js +326 -0
- package/dist/converge/historical-delivery-reconciliation.js.map +1 -0
- package/dist/converge/launch-guard.js +25 -6
- package/dist/converge/launch-guard.js.map +1 -1
- package/dist/converge/launch-record.d.ts +36 -0
- package/dist/converge/launch-record.js +9 -3
- package/dist/converge/launch-record.js.map +1 -1
- package/dist/converge/legacy-launch-health.d.ts +36 -0
- package/dist/converge/native-lock.d.ts +3 -1
- package/dist/converge/native-lock.js +4 -4
- package/dist/converge/native-lock.js.map +1 -1
- package/dist/converge/ordinary-pending-export.d.ts +3 -0
- package/dist/converge/ordinary-pending-export.js +64 -17
- package/dist/converge/ordinary-pending-export.js.map +1 -1
- package/dist/converge/ordinary-pending-package.d.ts +11 -0
- package/dist/converge/ordinary-pending-package.js +18 -4
- package/dist/converge/ordinary-pending-package.js.map +1 -1
- package/dist/converge/pending-finalization-receipt.d.ts +4 -0
- package/dist/converge/pending-finalization-receipt.js +102 -0
- package/dist/converge/pending-finalization-receipt.js.map +1 -0
- package/dist/converge/pending-legacy-resume.d.ts +42 -3
- package/dist/converge/pending-legacy-resume.js +164 -32
- package/dist/converge/pending-legacy-resume.js.map +1 -1
- package/dist/converge/pending-recovery-source.d.ts +114 -0
- package/dist/converge/pending-recovery-source.js +36 -1
- package/dist/converge/pending-recovery-source.js.map +1 -1
- package/dist/converge/process-identity.d.ts +51 -0
- package/dist/converge/process-identity.js +190 -0
- package/dist/converge/process-identity.js.map +1 -0
- package/dist/converge/recovery-launch.js +39 -17
- package/dist/converge/recovery-launch.js.map +1 -1
- package/dist/converge/recovery-state.d.ts +55 -0
- package/dist/converge/recovery-state.js +306 -0
- package/dist/converge/recovery-state.js.map +1 -0
- package/dist/converge/run-state.d.ts +43 -1
- package/dist/converge/run-state.js +252 -18
- package/dist/converge/run-state.js.map +1 -1
- package/dist/converge/semantic-state.d.ts +55 -0
- package/dist/converge/semantic-state.js +666 -0
- package/dist/converge/semantic-state.js.map +1 -0
- package/dist/converge/stale-report.d.ts +412 -0
- package/dist/converge/stale-report.js +42 -0
- package/dist/converge/stale-report.js.map +1 -1
- package/dist/converge/terminal-rejection.d.ts +1 -0
- package/dist/converge/terminal-rejection.js +6 -6
- package/dist/converge/terminal-rejection.js.map +1 -1
- package/dist/dispatch/async-lane.d.ts +12 -0
- package/dist/dispatch/async-lane.js +94 -0
- package/dist/dispatch/async-lane.js.map +1 -1
- package/dist/evidence/claim-recovery/validation/native-state.d.ts +22 -0
- package/dist/evidence/claim-recovery/validation/native-state.js +91 -9
- package/dist/evidence/claim-recovery/validation/native-state.js.map +1 -1
- package/dist/evidence/claim-recovery/validation/obligations.js +2 -1
- package/dist/evidence/claim-recovery/validation/obligations.js.map +1 -1
- package/dist/evidence/claim-recovery/validation/semantic-validation.d.ts +11 -0
- package/dist/evidence/claim-recovery/validation/semantic-validation.js +105 -28
- package/dist/evidence/claim-recovery/validation/semantic-validation.js.map +1 -1
- package/dist/evidence/claim-recovery/validation/types.d.ts +10 -0
- package/dist/evidence/original-run/lock-scope.d.ts +12 -6
- package/dist/evidence/original-run/lock-scope.js +12 -9
- package/dist/evidence/original-run/lock-scope.js.map +1 -1
- package/dist/index.js +74 -11
- package/dist/index.js.map +1 -1
- package/dist/telemetry/recovery/files.d.ts +4 -0
- package/dist/telemetry/recovery/files.js +15 -5
- package/dist/telemetry/recovery/files.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { guardedInputSha256, stableStringify } from '../report/run-header.js';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
export function validateOrdinaryPendingPackage(value, expected) {
|
|
3
4
|
const input = value?.guardedInput;
|
|
4
5
|
const specPresent = !!input && Object.hasOwn(input, 'spec');
|
|
@@ -15,16 +16,29 @@ export function validateOrdinaryPendingPackage(value, expected) {
|
|
|
15
16
|
return item && typeof item === 'object' && typeof role.name === 'string' && role.name.length > 0;
|
|
16
17
|
}) &&
|
|
17
18
|
(!specPresent || (input.spec && typeof input.spec === 'object'));
|
|
18
|
-
|
|
19
|
+
const cycleBacked = expected.cycle !== undefined;
|
|
20
|
+
const cycleFieldsValid = cycleBacked
|
|
21
|
+
? value.version === 2 && isDeepStrictEqual(value.cycle, expected.cycle) &&
|
|
22
|
+
value.attemptCap === expected.attemptCap && value.roundCap === expected.roundCap &&
|
|
23
|
+
value.attemptsUsed === expected.attemptsUsed && value.attemptsUsed === value.attempt &&
|
|
24
|
+
value.asyncAttribution === 'cycle-history-unattributed'
|
|
25
|
+
: value.version === undefined && value.cycle === undefined && value.attemptCap === undefined &&
|
|
26
|
+
value.roundCap === undefined && value.attemptsUsed === undefined && value.asyncAttribution === undefined;
|
|
27
|
+
if (!value || !cycleFieldsValid || value.target !== expected.target || value.headSha !== expected.headSha || value.baseSha !== expected.baseSha || value.attempt !== expected.attempt || value.round !== expected.round || value.pid !== expected.pid || !/^[a-f0-9]{40}$/.test(value.baseSha) ||
|
|
19
28
|
!Number.isSafeInteger(value.attempt) || value.attempt < 1 || !Number.isSafeInteger(value.round) || value.round < 1 ||
|
|
20
29
|
!Number.isSafeInteger(value.pid) || value.pid < 1 || !input || Object.keys(input).sort().join(',') !== keys.join(',') ||
|
|
21
30
|
!Array.isArray(value.retainedAsyncSha256) || [...value.retainedAsyncSha256].sort().join(',') !== [...expected.retainedAsyncSha256].sort().join(',') || input.head !== value.headSha || guardedInputSha256(input) !== expected.inputSha256)
|
|
22
31
|
throw new Error('ordinary_pending_package_mismatch');
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const cycleHistory = value.version === 2;
|
|
33
|
+
const descriptorHashes = Array.isArray(value.retainedAsync)
|
|
34
|
+
? value.retainedAsync.map(item => item?.sha256).sort() : [];
|
|
35
|
+
if (!structuredInput || !/^[a-f0-9]{40}$/.test(value.headSha) ||
|
|
36
|
+
(!cycleHistory && (!new Set(value.retainedAsyncSha256).size ||
|
|
37
|
+
new Set(value.retainedAsyncSha256).size !== value.retainedAsyncSha256.length)) ||
|
|
25
38
|
value.retainedAsyncSha256.some(hash => !digest(hash)) || !Array.isArray(value.retainedAsync) ||
|
|
26
39
|
value.retainedAsync.length !== value.retainedAsyncSha256.length ||
|
|
27
|
-
new Set(value.retainedAsync.map(item => item?.sha256)).size !== value.retainedAsync.length ||
|
|
40
|
+
(!cycleHistory && new Set(value.retainedAsync.map(item => item?.sha256)).size !== value.retainedAsync.length) ||
|
|
41
|
+
descriptorHashes.join(',') !== [...value.retainedAsyncSha256].sort().join(',') ||
|
|
28
42
|
value.retainedAsync.some(item => !item || !digest(item.sha256) || !value.retainedAsyncSha256.includes(item.sha256) ||
|
|
29
43
|
typeof item.model !== 'string' || !item.model || typeof item.role !== 'string' || !item.role ||
|
|
30
44
|
typeof item.provider !== 'string' || !item.provider || item.lane !== 'async' ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ordinary-pending-package.js","sourceRoot":"","sources":["../../src/converge/ordinary-pending-package.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"ordinary-pending-package.js","sourceRoot":"","sources":["../../src/converge/ordinary-pending-package.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type GuardedLaunchState } from './launch-record.js';
|
|
2
|
+
import type { ConvergeRunState } from './run-state.js';
|
|
3
|
+
/** Authenticate the one terminal transition that disposes an unknown cycle dispatch. */
|
|
4
|
+
export declare function verifyCyclePendingFinalization(common: string, state: ConvergeRunState, launch: GuardedLaunchState): Promise<boolean>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { launchSchema } from './launch-record.js';
|
|
6
|
+
import { convergeAttemptStatePath, validateConvergeAttemptState } from './attempt-budget.js';
|
|
7
|
+
import { convergeRunStatePath } from './run-state.js';
|
|
8
|
+
import { stableStringify } from '../report/run-header.js';
|
|
9
|
+
import { readStable } from '../telemetry/recovery/files.js';
|
|
10
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
11
|
+
const receiptSchema = z.object({
|
|
12
|
+
version: z.literal(2),
|
|
13
|
+
operation: z.literal('cycle-pending-finalize-only'),
|
|
14
|
+
target: z.string().min(1),
|
|
15
|
+
headSha: z.string().regex(/^[a-f0-9]{40}$/),
|
|
16
|
+
baseSha: z.string().regex(/^[a-f0-9]{40}$/),
|
|
17
|
+
inputSha256: digest,
|
|
18
|
+
sourceDigest: digest,
|
|
19
|
+
migrationPackageSha256: digest,
|
|
20
|
+
finalizedAttempt: z.number().int().positive().safe(),
|
|
21
|
+
round: z.number().int().positive().safe(),
|
|
22
|
+
originalPid: z.number().int().positive().safe(),
|
|
23
|
+
attemptsUsed: z.number().int().positive().safe(),
|
|
24
|
+
cap: z.number().int().positive().safe(),
|
|
25
|
+
nextFreeAttempt: z.number().int().positive().safe(),
|
|
26
|
+
blockingOutcome: z.literal('unknown'),
|
|
27
|
+
cycleId: z.string().uuid(),
|
|
28
|
+
operationId: z.string().uuid(),
|
|
29
|
+
repo: z.string().min(1),
|
|
30
|
+
prNumber: z.number().int().positive().safe(),
|
|
31
|
+
attemptCap: z.number().int().positive().safe(),
|
|
32
|
+
roundCap: z.number().int().positive().safe(),
|
|
33
|
+
asyncAttribution: z.literal('cycle-history-unattributed'),
|
|
34
|
+
retainedAsyncSha256: z.array(digest),
|
|
35
|
+
sourceNativeStateSha256: digest,
|
|
36
|
+
finalizedNativeStateSha256: digest,
|
|
37
|
+
sourceAttemptStateSha256: digest,
|
|
38
|
+
receiptDigest: digest,
|
|
39
|
+
}).strict();
|
|
40
|
+
/** Authenticate the one terminal transition that disposes an unknown cycle dispatch. */
|
|
41
|
+
export async function verifyCyclePendingFinalization(common, state, launch) {
|
|
42
|
+
const recovery = launch.pendingRecovery;
|
|
43
|
+
const packageDigest = recovery?.migrationPackageSha256;
|
|
44
|
+
if (!state.cycle || launch.status !== 'failed' || !recovery || !packageDigest)
|
|
45
|
+
return false;
|
|
46
|
+
try {
|
|
47
|
+
const stored = await readStable(join(common, 'rcl-converge-pending-finalizations', packageDigest, 'receipt.json'));
|
|
48
|
+
const receipt = receiptSchema.parse(JSON.parse(stored.text));
|
|
49
|
+
const { receiptDigest, ...body } = receipt;
|
|
50
|
+
if (receiptDigest !== createHash('sha256').update(stableStringify(body)).digest('hex') ||
|
|
51
|
+
receipt.target !== state.target || receipt.headSha !== launch.headSha ||
|
|
52
|
+
receipt.inputSha256 !== launch.inputSha256 || receipt.sourceDigest !== recovery.sourceDigest ||
|
|
53
|
+
receipt.migrationPackageSha256 !== packageDigest || receipt.finalizedAttempt !== launch.attempt ||
|
|
54
|
+
receipt.round !== launch.round || receipt.originalPid !== launch.pid ||
|
|
55
|
+
receipt.attemptsUsed !== launch.attempt || receipt.nextFreeAttempt !== launch.attempt + 1 ||
|
|
56
|
+
receipt.cap !== receipt.attemptCap || receipt.cycleId !== state.cycle.id ||
|
|
57
|
+
receipt.operationId !== state.cycle.operationId || receipt.repo.toLowerCase() !== state.cycle.repo ||
|
|
58
|
+
receipt.prNumber !== state.cycle.prNumber || receipt.roundCap !== state.roundCap ||
|
|
59
|
+
receipt.sourceNativeStateSha256 !== recovery.nativeStateSha256 ||
|
|
60
|
+
receipt.sourceAttemptStateSha256 !== recovery.attemptStateSha256 ||
|
|
61
|
+
receipt.retainedAsyncSha256.join(',') !== [...recovery.retainedAsyncSha256].sort().join(','))
|
|
62
|
+
return false;
|
|
63
|
+
const retainedRoot = join(common, 'rcl-converge-pending-finalizations', packageDigest);
|
|
64
|
+
const [native, attempts, manifest, retainedAttempts, retainedNative] = await Promise.all([
|
|
65
|
+
readStable(convergeRunStatePath(common, state.target)),
|
|
66
|
+
readStable(convergeAttemptStatePath(common, state.target)),
|
|
67
|
+
readStable(join(common, 'rcl-converge-pending-recovery', recovery.sourceDigest, 'manifest.json')),
|
|
68
|
+
readStable(join(retainedRoot, 'source-attempt-state.json')),
|
|
69
|
+
readStable(join(retainedRoot, 'finalized-native-state.json')),
|
|
70
|
+
]);
|
|
71
|
+
if (native.sha256 !== receipt.finalizedNativeStateSha256 ||
|
|
72
|
+
attempts.sha256 !== receipt.sourceAttemptStateSha256 ||
|
|
73
|
+
retainedAttempts.sha256 !== receipt.sourceAttemptStateSha256 ||
|
|
74
|
+
retainedNative.sha256 !== receipt.finalizedNativeStateSha256)
|
|
75
|
+
return false;
|
|
76
|
+
const sourceAttempts = validateConvergeAttemptState(JSON.parse(retainedAttempts.text), state.target, 'retained cycle pending attempt state');
|
|
77
|
+
const finalizedState = JSON.parse(retainedNative.text);
|
|
78
|
+
const finalizedLaunch = launchSchema.safeParse(finalizedState.lastLaunch);
|
|
79
|
+
if (sourceAttempts.cap !== receipt.cap || sourceAttempts.attemptsUsed !== receipt.attemptsUsed ||
|
|
80
|
+
!isDeepStrictEqual(sourceAttempts.cycle, state.cycle) ||
|
|
81
|
+
sourceAttempts.attempts.at(-1)?.attempt !== receipt.finalizedAttempt ||
|
|
82
|
+
sourceAttempts.attempts.at(-1)?.pid !== receipt.originalPid ||
|
|
83
|
+
finalizedState.target !== state.target || finalizedState.roundCap !== receipt.roundCap ||
|
|
84
|
+
!isDeepStrictEqual(finalizedState.cycle, state.cycle) || !finalizedLaunch.success ||
|
|
85
|
+
!isDeepStrictEqual(finalizedLaunch.data, launch))
|
|
86
|
+
return false;
|
|
87
|
+
const expectedManifest = { version: 1, sourceDigest: recovery.sourceDigest,
|
|
88
|
+
blockingOutcome: 'unknown', artifacts: receipt.retainedAsyncSha256.map(sha256 => ({ sha256 })).sort((a, b) => a.sha256.localeCompare(b.sha256)) };
|
|
89
|
+
if (!isDeepStrictEqual(JSON.parse(manifest.text), expectedManifest))
|
|
90
|
+
return false;
|
|
91
|
+
await Promise.all(receipt.retainedAsyncSha256.map(async (sha256) => {
|
|
92
|
+
if ((await readStable(join(common, 'rcl-converge-pending-recovery', recovery.sourceDigest, sha256))).sha256 !== sha256) {
|
|
93
|
+
throw new Error('pending_finalization_artifact_mismatch');
|
|
94
|
+
}
|
|
95
|
+
}));
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=pending-finalization-receipt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-finalization-receipt.js","sourceRoot":"","sources":["../../src/converge/pending-finalization-receipt.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,9 +7,10 @@ import { type NativeTargetOwnership } from './target-ownership.js';
|
|
|
7
7
|
import { type OriginalLaunch } from '../dispatch/original-launch.js';
|
|
8
8
|
import type { CapturedPreparedCouncil } from '../dispatch/capture-council.js';
|
|
9
9
|
import type { CheckpointJournal } from '../dispatch/checkpoint.js';
|
|
10
|
-
import type
|
|
10
|
+
import { consumeBoundAsyncHistory, type AsyncResultReference } from '../dispatch/async-lane.js';
|
|
11
11
|
import { type PendingRecoverySource } from './pending-recovery-source.js';
|
|
12
12
|
import { type OrdinaryPendingPackage } from './ordinary-pending-package.js';
|
|
13
|
+
import type { ReviewCycleRemote } from './review-cycle.js';
|
|
13
14
|
export interface PendingLegacyResumeExecution {
|
|
14
15
|
context: {
|
|
15
16
|
target: string;
|
|
@@ -46,6 +47,7 @@ export interface PendingLegacyResumeOptions {
|
|
|
46
47
|
migrationPackage?: OrdinaryPendingPackage;
|
|
47
48
|
preview?: boolean;
|
|
48
49
|
previewMode?: 'combined' | 'finalize-only';
|
|
50
|
+
cycleRemote?: ReviewCycleRemote;
|
|
49
51
|
}
|
|
50
52
|
export interface PendingLegacyResumeResult {
|
|
51
53
|
claim: ConvergeAttemptClaim;
|
|
@@ -75,8 +77,17 @@ export interface OrdinaryPendingFinalizeOptions {
|
|
|
75
77
|
loadRetainedAsync: () => Promise<AsyncResultReference[]>;
|
|
76
78
|
/** Fault-injection boundary for proving archive-complete/native-write recovery. */
|
|
77
79
|
writeFinalizedState?: typeof writeState;
|
|
80
|
+
cycleHistory?: {
|
|
81
|
+
storeDir?: string;
|
|
82
|
+
resolveStoreDir?: () => Promise<string>;
|
|
83
|
+
targetKey: string;
|
|
84
|
+
maxResults: number;
|
|
85
|
+
};
|
|
86
|
+
/** Fault-injection boundary for proving idempotent history consumption. */
|
|
87
|
+
consumeCycleHistory?: typeof consumeBoundAsyncHistory;
|
|
88
|
+
cycleRemote?: ReviewCycleRemote;
|
|
78
89
|
}
|
|
79
|
-
declare const ordinaryPendingFinalizationReceiptSchema: z.ZodObject<{
|
|
90
|
+
declare const ordinaryPendingFinalizationReceiptSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
80
91
|
version: z.ZodLiteral<1>;
|
|
81
92
|
operation: z.ZodLiteral<"ordinary-pending-finalize-only">;
|
|
82
93
|
target: z.ZodString;
|
|
@@ -97,7 +108,35 @@ declare const ordinaryPendingFinalizationReceiptSchema: z.ZodObject<{
|
|
|
97
108
|
sourceAttemptStateSha256: z.ZodString;
|
|
98
109
|
finalizedNativeStateSha256: z.ZodString;
|
|
99
110
|
receiptDigest: z.ZodString;
|
|
100
|
-
}, z.core.$strict
|
|
111
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
112
|
+
target: z.ZodString;
|
|
113
|
+
round: z.ZodNumber;
|
|
114
|
+
headSha: z.ZodString;
|
|
115
|
+
inputSha256: z.ZodString;
|
|
116
|
+
baseSha: z.ZodString;
|
|
117
|
+
originalPid: z.ZodNumber;
|
|
118
|
+
sourceDigest: z.ZodString;
|
|
119
|
+
blockingOutcome: z.ZodLiteral<"unknown">;
|
|
120
|
+
migrationPackageSha256: z.ZodString;
|
|
121
|
+
attemptsUsed: z.ZodNumber;
|
|
122
|
+
cap: z.ZodNumber;
|
|
123
|
+
sourceNativeStateSha256: z.ZodString;
|
|
124
|
+
finalizedNativeStateSha256: z.ZodString;
|
|
125
|
+
sourceAttemptStateSha256: z.ZodString;
|
|
126
|
+
finalizedAttempt: z.ZodNumber;
|
|
127
|
+
nextFreeAttempt: z.ZodNumber;
|
|
128
|
+
version: z.ZodLiteral<2>;
|
|
129
|
+
operation: z.ZodLiteral<"cycle-pending-finalize-only">;
|
|
130
|
+
retainedAsyncSha256: z.ZodArray<z.ZodString>;
|
|
131
|
+
cycleId: z.ZodString;
|
|
132
|
+
operationId: z.ZodString;
|
|
133
|
+
repo: z.ZodString;
|
|
134
|
+
prNumber: z.ZodNumber;
|
|
135
|
+
attemptCap: z.ZodNumber;
|
|
136
|
+
roundCap: z.ZodNumber;
|
|
137
|
+
asyncAttribution: z.ZodLiteral<"cycle-history-unattributed">;
|
|
138
|
+
receiptDigest: z.ZodString;
|
|
139
|
+
}, z.core.$strict>]>;
|
|
101
140
|
export type OrdinaryPendingFinalizationReceipt = z.infer<typeof ordinaryPendingFinalizationReceiptSchema>;
|
|
102
141
|
export interface OrdinaryPendingFinalizeResult {
|
|
103
142
|
receipt: OrdinaryPendingFinalizationReceipt;
|
|
@@ -10,6 +10,7 @@ import { claimConvergeAttempt, convergeAttemptStatePath, previewConvergeAttemptS
|
|
|
10
10
|
import { withRecoveryTarget } from './target-ownership.js';
|
|
11
11
|
import { bindOriginalCouncil } from '../dispatch/original-execution.js';
|
|
12
12
|
import { createOriginalLaunch } from '../dispatch/original-launch.js';
|
|
13
|
+
import { consumeBoundAsyncHistory, snapshotAsyncHistory } from '../dispatch/async-lane.js';
|
|
13
14
|
import { uuidv7 } from '../report/uuid.js';
|
|
14
15
|
import { captureSupplementalAsync } from '../report/supplemental-async.js';
|
|
15
16
|
import { stableStringify } from '../report/run-header.js';
|
|
@@ -17,8 +18,9 @@ import { readStable } from '../telemetry/recovery/files.js';
|
|
|
17
18
|
import { serializeRecoveryDocument } from '../evidence/original-run/journal.js';
|
|
18
19
|
import { prepareLockRoot } from '../evidence/original-run/lock-path.js';
|
|
19
20
|
import { retainStaleFile } from './stale-report-storage.js';
|
|
20
|
-
import { createPendingRecoverySource, createOrdinaryMigrationSource, pendingRecoverySourceSchema } from './pending-recovery-source.js';
|
|
21
|
+
import { createPendingRecoverySource, createOrdinaryMigrationSource, createCycleMigrationSource, pendingRecoverySourceSchema } from './pending-recovery-source.js';
|
|
21
22
|
import { validateOrdinaryPendingPackage } from './ordinary-pending-package.js';
|
|
23
|
+
import { assertReviewCyclePair } from './fresh-review.js';
|
|
22
24
|
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
23
25
|
const ordinaryPendingFinalizationReceiptBodySchema = z.object({
|
|
24
26
|
version: z.literal(1),
|
|
@@ -41,18 +43,43 @@ const ordinaryPendingFinalizationReceiptBodySchema = z.object({
|
|
|
41
43
|
sourceAttemptStateSha256: digestSchema,
|
|
42
44
|
finalizedNativeStateSha256: digestSchema,
|
|
43
45
|
}).strict();
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const cyclePendingFinalizationReceiptBodySchema = ordinaryPendingFinalizationReceiptBodySchema.omit({
|
|
47
|
+
version: true,
|
|
48
|
+
operation: true,
|
|
49
|
+
retainedAsyncSha256: true,
|
|
50
|
+
}).extend({
|
|
51
|
+
version: z.literal(2),
|
|
52
|
+
operation: z.literal('cycle-pending-finalize-only'),
|
|
53
|
+
retainedAsyncSha256: z.array(digestSchema),
|
|
54
|
+
cycleId: z.string().uuid(),
|
|
55
|
+
operationId: z.string().uuid(),
|
|
56
|
+
repo: z.string().min(1),
|
|
57
|
+
prNumber: z.number().int().positive().safe(),
|
|
58
|
+
attemptCap: z.number().int().positive().safe(),
|
|
59
|
+
roundCap: z.number().int().positive().safe(),
|
|
60
|
+
asyncAttribution: z.literal('cycle-history-unattributed'),
|
|
61
|
+
}).strict();
|
|
62
|
+
const pendingFinalizationReceiptBodySchema = z.union([
|
|
63
|
+
ordinaryPendingFinalizationReceiptBodySchema,
|
|
64
|
+
cyclePendingFinalizationReceiptBodySchema,
|
|
65
|
+
]);
|
|
66
|
+
const ordinaryPendingFinalizationReceiptSchema = z.union([
|
|
67
|
+
ordinaryPendingFinalizationReceiptBodySchema.extend({ receiptDigest: digestSchema }).strict(),
|
|
68
|
+
cyclePendingFinalizationReceiptBodySchema.extend({ receiptDigest: digestSchema }).strict(),
|
|
69
|
+
]).superRefine((receipt, context) => {
|
|
47
70
|
const { receiptDigest, ...body } = receipt;
|
|
48
71
|
if (receiptDigest !== createHash('sha256').update(stableStringify(body)).digest('hex')) {
|
|
49
72
|
context.addIssue({ code: 'custom', message: 'Pending finalization receipt digest is invalid' });
|
|
50
73
|
}
|
|
51
74
|
if (receipt.nextFreeAttempt !== receipt.finalizedAttempt + 1 ||
|
|
52
75
|
receipt.attemptsUsed !== receipt.finalizedAttempt || receipt.attemptsUsed > receipt.cap ||
|
|
53
|
-
|
|
76
|
+
(receipt.version === 1 && (receipt.retainedAsyncSha256.length === 0 ||
|
|
77
|
+
new Set(receipt.retainedAsyncSha256).size !== receipt.retainedAsyncSha256.length))) {
|
|
54
78
|
context.addIssue({ code: 'custom', message: 'Pending finalization accounting is inconsistent' });
|
|
55
79
|
}
|
|
80
|
+
if (receipt.version === 2 && (receipt.attemptCap !== receipt.cap || receipt.round > receipt.roundCap)) {
|
|
81
|
+
context.addIssue({ code: 'custom', message: 'Cycle pending finalization accounting is inconsistent' });
|
|
82
|
+
}
|
|
56
83
|
});
|
|
57
84
|
function fail(code) { throw new Error(`pending_legacy_resume_${code}`); }
|
|
58
85
|
function defaultOwnerAlive(pid) {
|
|
@@ -90,12 +117,18 @@ function sourceFromFinalized(target, launch, attempts, migrationPackage) {
|
|
|
90
117
|
const migrationPackageSha256 = createHash('sha256').update(stableStringify(migrationPackage)).digest('hex');
|
|
91
118
|
if (migrationPackageSha256 !== recovery.migrationPackageSha256)
|
|
92
119
|
fail('finalization_mismatch');
|
|
93
|
-
const
|
|
120
|
+
const common = { target, headSha: launch.headSha,
|
|
94
121
|
inputSha256: launch.inputSha256, pendingAttempt: launch.attempt, round: launch.round,
|
|
95
122
|
originalPid: launch.pid, startedAt: launch.startedAt, blockingOutcome: recovery.blockingOutcome,
|
|
96
123
|
reason: recovery.reason, nativeStateSha256: recovery.nativeStateSha256,
|
|
97
124
|
attemptStateSha256: recovery.attemptStateSha256, retainedAsyncSha256: recovery.retainedAsyncSha256,
|
|
98
|
-
migrationPackageSha256 }
|
|
125
|
+
migrationPackageSha256 };
|
|
126
|
+
const source = migrationPackage.version === 2
|
|
127
|
+
? createCycleMigrationSource({ version: 2, ...common, cycle: migrationPackage.cycle,
|
|
128
|
+
cycleId: migrationPackage.cycle.id, operationId: migrationPackage.cycle.operationId,
|
|
129
|
+
attemptCap: migrationPackage.attemptCap, roundCap: migrationPackage.roundCap,
|
|
130
|
+
attemptsUsed: migrationPackage.attemptsUsed, asyncAttribution: 'cycle-history-unattributed' })
|
|
131
|
+
: createOrdinaryMigrationSource({ version: 1, ...common });
|
|
99
132
|
if (source.digest !== recovery.sourceDigest)
|
|
100
133
|
fail('finalization_mismatch');
|
|
101
134
|
return source;
|
|
@@ -112,16 +145,47 @@ function sourceFromFinalized(target, launch, attempts, migrationPackage) {
|
|
|
112
145
|
digest: recovery.sourceDigest,
|
|
113
146
|
});
|
|
114
147
|
}
|
|
148
|
+
function packageExpected(state, attempts, current, input) {
|
|
149
|
+
return {
|
|
150
|
+
target: input.target, headSha: current.headSha, inputSha256: current.inputSha256,
|
|
151
|
+
baseSha: input.baseSha, attempt: current.attempt, round: current.round, pid: current.pid,
|
|
152
|
+
retainedAsyncSha256: input.retainedAsyncSha256,
|
|
153
|
+
...(state.cycle ? { cycle: state.cycle, attemptCap: attempts.cap,
|
|
154
|
+
roundCap: state.roundCap, attemptsUsed: attempts.attemptsUsed } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function assertCycleRecovery(options, state, attempts, packet) {
|
|
158
|
+
if (!state.cycle) {
|
|
159
|
+
if (packet.version === 2)
|
|
160
|
+
fail('cycle_mismatch');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
await assertReviewCyclePair(options.gitCommonDir, options.target, state.cycle);
|
|
164
|
+
if (packet.version !== 2 || !isDeepStrictEqual(packet.cycle, state.cycle))
|
|
165
|
+
fail('cycle_mismatch');
|
|
166
|
+
const remote = options.cycleRemote;
|
|
167
|
+
if (!remote || remote.repo.toLowerCase() !== state.cycle.repo ||
|
|
168
|
+
remote.prNumber !== state.cycle.prNumber || remote.url !== state.cycle.url)
|
|
169
|
+
fail('cycle_remote_mismatch');
|
|
170
|
+
if ((await remote.current())?.id !== state.cycle.id)
|
|
171
|
+
fail('cycle_superseded');
|
|
172
|
+
if (attempts.cap !== packet.attemptCap || state.roundCap !== packet.roundCap ||
|
|
173
|
+
attempts.attemptsUsed !== packet.attemptsUsed)
|
|
174
|
+
fail('cycle_accounting_mismatch');
|
|
175
|
+
}
|
|
115
176
|
function validateOptions(input) {
|
|
116
177
|
const hashes = input?.retainedAsyncSha256;
|
|
178
|
+
const cycleFinalizePreview = input?.migrationPackage?.version === 2 &&
|
|
179
|
+
input.previewMode === 'finalize-only';
|
|
117
180
|
if (!input || typeof input.gitCommonDir !== 'string' || typeof input.target !== 'string' || !input.target.trim() ||
|
|
118
181
|
!/^[a-f0-9]{40}$/.test(input.headSha ?? '') ||
|
|
119
182
|
(input.migrationPackage && !/^[a-f0-9]{40}$/.test(input.baseSha ?? '')) ||
|
|
120
183
|
!/^[a-f0-9]{64}$/.test(input.pendingInputSha256 ?? '') ||
|
|
121
184
|
!/^[a-f0-9]{64}$/.test(input.recoveryInputSha256 ?? '') ||
|
|
122
185
|
typeof input.retryReason !== 'string' || !input.retryReason.trim() || input.retryReason.length > 500 ||
|
|
123
|
-
!Array.isArray(hashes) || hashes.
|
|
124
|
-
new Set(hashes).size !== hashes.length ||
|
|
186
|
+
!Array.isArray(hashes) || hashes.some(value => !/^[a-f0-9]{64}$/.test(value)) ||
|
|
187
|
+
(!cycleFinalizePreview && (hashes.length === 0 || new Set(hashes).size !== hashes.length)) ||
|
|
188
|
+
typeof input.validate !== 'function' ||
|
|
125
189
|
typeof input.run !== 'function' || typeof input.loadRetainedAsync !== 'function' ||
|
|
126
190
|
(!input.migrationPackage && !input.legacyRetry) ||
|
|
127
191
|
!Number.isSafeInteger(input.maxAttempts) || input.maxAttempts < 1 ||
|
|
@@ -134,18 +198,48 @@ function validateOptions(input) {
|
|
|
134
198
|
}
|
|
135
199
|
function validateFinalizeOptions(input) {
|
|
136
200
|
const hashes = input?.retainedAsyncSha256;
|
|
201
|
+
const cycleBacked = input?.migrationPackage?.version === 2;
|
|
202
|
+
const hasStoreDir = typeof input?.cycleHistory?.storeDir === 'string' &&
|
|
203
|
+
input.cycleHistory.storeDir.trim().length > 0;
|
|
204
|
+
const hasStoreResolver = typeof input?.cycleHistory?.resolveStoreDir === 'function';
|
|
137
205
|
if (!input || typeof input.gitCommonDir !== 'string' || typeof input.target !== 'string' || !input.target.trim() ||
|
|
138
206
|
!/^[a-f0-9]{40}$/.test(input.headSha ?? '') || !/^[a-f0-9]{40}$/.test(input.baseSha ?? '') ||
|
|
139
207
|
!/^[a-f0-9]{64}$/.test(input.pendingInputSha256 ?? '') ||
|
|
140
208
|
!/^[a-f0-9]{64}$/.test(input.nativeStateSha256 ?? '') ||
|
|
141
209
|
!/^[a-f0-9]{64}$/.test(input.attemptStateSha256 ?? '') ||
|
|
142
|
-
!Array.isArray(hashes) || hashes.
|
|
143
|
-
new Set(hashes).size !== hashes.length ||
|
|
210
|
+
!Array.isArray(hashes) || hashes.some(value => !/^[a-f0-9]{64}$/.test(value)) ||
|
|
211
|
+
(!cycleBacked && (hashes.length === 0 || new Set(hashes).size !== hashes.length)) ||
|
|
212
|
+
typeof input.loadRetainedAsync !== 'function' ||
|
|
144
213
|
(input.writeFinalizedState !== undefined && typeof input.writeFinalizedState !== 'function') ||
|
|
214
|
+
(input.consumeCycleHistory !== undefined && typeof input.consumeCycleHistory !== 'function') ||
|
|
215
|
+
(cycleBacked && (!input.cycleHistory || hasStoreDir === hasStoreResolver ||
|
|
216
|
+
typeof input.cycleHistory.targetKey !== 'string' || !input.cycleHistory.targetKey.trim() ||
|
|
217
|
+
!Number.isSafeInteger(input.cycleHistory.maxResults) || input.cycleHistory.maxResults < 1)) ||
|
|
145
218
|
!input.migrationPackage || !Number.isSafeInteger(input.maxAttempts) || input.maxAttempts < 1) {
|
|
146
219
|
fail('invalid_input');
|
|
147
220
|
}
|
|
148
221
|
}
|
|
222
|
+
async function reconcileFinalizedCycleHistory(options, mode) {
|
|
223
|
+
if (options.migrationPackage.version !== 2)
|
|
224
|
+
return;
|
|
225
|
+
const history = options.cycleHistory;
|
|
226
|
+
let storeDir;
|
|
227
|
+
try {
|
|
228
|
+
storeDir = history.storeDir ?? await history.resolveStoreDir();
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (mode !== 'new-receipt' && error.code === 'ENOENT')
|
|
232
|
+
return;
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
if (mode === 'advanced-replay') {
|
|
236
|
+
const live = await snapshotAsyncHistory(storeDir, history.targetKey, history.maxResults);
|
|
237
|
+
if (live.artifacts.length > 0)
|
|
238
|
+
fail('cycle_history_ambiguous_after_successor');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
await (options.consumeCycleHistory ?? consumeBoundAsyncHistory)(storeDir, history.targetKey, options.retainedAsyncSha256, history.maxResults);
|
|
242
|
+
}
|
|
149
243
|
async function archiveAsync(common, source, artifacts, packet) {
|
|
150
244
|
validateAsyncArtifacts(source, artifacts, packet);
|
|
151
245
|
const directory = await prepareLockRoot(join(common, 'rcl-converge-pending-recovery', source.digest));
|
|
@@ -269,7 +363,7 @@ async function assertFinalizedStateMatchesReceipt(common, receipt) {
|
|
|
269
363
|
return { native: native.raw, attempts: attempts.raw, exact: false };
|
|
270
364
|
}
|
|
271
365
|
function createFinalizationReceipt(input) {
|
|
272
|
-
const body =
|
|
366
|
+
const body = pendingFinalizationReceiptBodySchema.parse(input);
|
|
273
367
|
return ordinaryPendingFinalizationReceiptSchema.parse({
|
|
274
368
|
...body,
|
|
275
369
|
receiptDigest: createHash('sha256').update(stableStringify(body)).digest('hex'),
|
|
@@ -284,10 +378,16 @@ function assertReceiptMatchesInput(receipt, input, migrationPackageSha256) {
|
|
|
284
378
|
receipt.retainedAsyncSha256.join(',') !== [...input.retainedAsyncSha256].sort().join(',')) {
|
|
285
379
|
fail('finalization_receipt_mismatch');
|
|
286
380
|
}
|
|
381
|
+
if (receipt.version === 2 && (input.migrationPackage.cycle?.id !== receipt.cycleId ||
|
|
382
|
+
input.migrationPackage.cycle.operationId !== receipt.operationId))
|
|
383
|
+
fail('finalization_receipt_mismatch');
|
|
287
384
|
return validateOrdinaryPendingPackage(input.migrationPackage, {
|
|
288
385
|
target: receipt.target, headSha: receipt.headSha, inputSha256: receipt.inputSha256,
|
|
289
386
|
baseSha: receipt.baseSha, attempt: receipt.finalizedAttempt, round: receipt.round,
|
|
290
387
|
pid: receipt.originalPid, retainedAsyncSha256: receipt.retainedAsyncSha256,
|
|
388
|
+
...(receipt.version === 2 ? { cycle: input.migrationPackage.cycle,
|
|
389
|
+
attemptCap: receipt.attemptCap, roundCap: receipt.roundCap,
|
|
390
|
+
attemptsUsed: receipt.attemptsUsed } : {}),
|
|
291
391
|
});
|
|
292
392
|
}
|
|
293
393
|
async function readArchivedAsync(common, sourceDigest, retainedAsyncSha256) {
|
|
@@ -328,6 +428,13 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
328
428
|
if (existingReceipt) {
|
|
329
429
|
const packet = assertReceiptMatchesInput(existingReceipt, options, migrationPackageSha256);
|
|
330
430
|
const state = await assertFinalizedStateMatchesReceipt(options.gitCommonDir, existingReceipt);
|
|
431
|
+
const [currentRun, currentAttempts] = await Promise.all([
|
|
432
|
+
loadConvergeRunState(options.gitCommonDir, options.target),
|
|
433
|
+
previewConvergeAttemptState(options.gitCommonDir, options.target),
|
|
434
|
+
]);
|
|
435
|
+
if (!currentRun || !currentAttempts)
|
|
436
|
+
fail('finalization_receipt_state_mismatch');
|
|
437
|
+
await assertCycleRecovery(options, currentRun, currentAttempts, packet.version === 2 ? { ...packet, attemptsUsed: currentAttempts.attemptsUsed } : packet);
|
|
331
438
|
validateAsyncArtifacts({ retainedAsyncSha256: existingReceipt.retainedAsyncSha256 }, await readArchivedAsync(options.gitCommonDir, existingReceipt.sourceDigest, existingReceipt.retainedAsyncSha256), packet);
|
|
332
439
|
const snapshotsPresent = await finalizationSnapshotsPresent(options.gitCommonDir, existingReceipt);
|
|
333
440
|
if (!snapshotsPresent) {
|
|
@@ -335,6 +442,7 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
335
442
|
fail('finalization_receipt_state_mismatch');
|
|
336
443
|
await retainFinalizationSnapshots(options.gitCommonDir, existingReceipt, state.attempts, state.native);
|
|
337
444
|
}
|
|
445
|
+
await reconcileFinalizedCycleHistory(options, state.exact ? 'exact-replay' : 'advanced-replay');
|
|
338
446
|
return { receipt: existingReceipt, reusedReceipt: true,
|
|
339
447
|
snapshotBindingUpgraded: !snapshotsPresent };
|
|
340
448
|
}
|
|
@@ -351,11 +459,8 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
351
459
|
current.inputSha256 !== options.pendingInputSha256 || !['pending', 'failed'].includes(current.status)) {
|
|
352
460
|
fail('launch_mismatch');
|
|
353
461
|
}
|
|
354
|
-
const packet = validateOrdinaryPendingPackage(options.migrationPackage,
|
|
355
|
-
|
|
356
|
-
baseSha: options.baseSha, attempt: current.attempt, round: current.round, pid: current.pid,
|
|
357
|
-
retainedAsyncSha256: options.retainedAsyncSha256,
|
|
358
|
-
});
|
|
462
|
+
const packet = validateOrdinaryPendingPackage(options.migrationPackage, packageExpected(state, attempts, current, options));
|
|
463
|
+
await assertCycleRecovery(options, state, attempts, packet);
|
|
359
464
|
let source;
|
|
360
465
|
let sourceAttemptStateBytes;
|
|
361
466
|
if (current.status === 'pending') {
|
|
@@ -366,12 +471,18 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
366
471
|
readStable(convergeAttemptStatePath(options.gitCommonDir, options.target)),
|
|
367
472
|
]);
|
|
368
473
|
sourceAttemptStateBytes = attemptBytes.raw;
|
|
369
|
-
|
|
474
|
+
const commonSource = { target: options.target,
|
|
370
475
|
headSha: current.headSha, inputSha256: current.inputSha256, pendingAttempt: current.attempt,
|
|
371
476
|
round: current.round, originalPid: current.pid, startedAt: current.startedAt,
|
|
372
477
|
blockingOutcome: 'unknown', reason: 'coordinator_exited_without_durable_blocking_receipts',
|
|
373
478
|
nativeStateSha256: nativeBytes.sha256, attemptStateSha256: attemptBytes.sha256,
|
|
374
|
-
retainedAsyncSha256: options.retainedAsyncSha256, migrationPackageSha256 }
|
|
479
|
+
retainedAsyncSha256: options.retainedAsyncSha256, migrationPackageSha256 };
|
|
480
|
+
source = packet.version === 2
|
|
481
|
+
? createCycleMigrationSource({ version: 2, ...commonSource, cycle: state.cycle,
|
|
482
|
+
cycleId: state.cycle.id, operationId: state.cycle.operationId,
|
|
483
|
+
attemptCap: attempts.cap, roundCap: state.roundCap, attemptsUsed: attempts.attemptsUsed,
|
|
484
|
+
asyncAttribution: 'cycle-history-unattributed' })
|
|
485
|
+
: createOrdinaryMigrationSource({ version: 1, ...commonSource });
|
|
375
486
|
}
|
|
376
487
|
else {
|
|
377
488
|
source = sourceFromFinalized(options.target, current, attempts, packet);
|
|
@@ -399,7 +510,7 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
399
510
|
// Construct and validate the exact receipt before the first write. The
|
|
400
511
|
// native serializer is shared with writeState, so its post-state digest is
|
|
401
512
|
// known without publishing a partially validated operation.
|
|
402
|
-
const
|
|
513
|
+
const receiptCommon = {
|
|
403
514
|
target: options.target, headSha: current.headSha, baseSha: options.baseSha,
|
|
404
515
|
inputSha256: current.inputSha256, sourceDigest: source.digest, migrationPackageSha256,
|
|
405
516
|
finalizedAttempt: current.attempt, round: current.round, originalPid: current.pid,
|
|
@@ -407,7 +518,15 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
407
518
|
blockingOutcome: 'unknown', retainedAsyncSha256: options.retainedAsyncSha256,
|
|
408
519
|
sourceNativeStateSha256: source.nativeStateSha256,
|
|
409
520
|
sourceAttemptStateSha256: source.attemptStateSha256,
|
|
410
|
-
finalizedNativeStateSha256
|
|
521
|
+
finalizedNativeStateSha256
|
|
522
|
+
};
|
|
523
|
+
const receipt = packet.version === 2
|
|
524
|
+
? createFinalizationReceipt({ version: 2, operation: 'cycle-pending-finalize-only', ...receiptCommon,
|
|
525
|
+
cycleId: state.cycle.id, operationId: state.cycle.operationId,
|
|
526
|
+
repo: state.cycle.repo, prNumber: state.cycle.prNumber,
|
|
527
|
+
attemptCap: attempts.cap, roundCap: state.roundCap,
|
|
528
|
+
asyncAttribution: 'cycle-history-unattributed' })
|
|
529
|
+
: createFinalizationReceipt({ version: 1, operation: 'ordinary-pending-finalize-only', ...receiptCommon });
|
|
411
530
|
if (current.status === 'pending') {
|
|
412
531
|
await preflightConvergeRunStateWrite(options.gitCommonDir, state, ownership);
|
|
413
532
|
}
|
|
@@ -424,6 +543,10 @@ export async function finalizeOrdinaryPendingLaunch(input) {
|
|
|
424
543
|
const finalizedAttempts = await readStable(convergeAttemptStatePath(options.gitCommonDir, options.target));
|
|
425
544
|
sourceAttemptStateBytes ??= finalizedAttempts.raw;
|
|
426
545
|
await retainFinalizationSnapshots(options.gitCommonDir, receipt, sourceAttemptStateBytes, finalizedNative.raw);
|
|
546
|
+
// Cycle history is consumed while the target remains recovery-locked and
|
|
547
|
+
// before the terminal receipt makes a successor eligible. Partial cleanup
|
|
548
|
+
// is safe to retry because consumption validates occurrence subsets.
|
|
549
|
+
await reconcileFinalizedCycleHistory(options, 'new-receipt');
|
|
427
550
|
await retainFinalizationReceipt(options.gitCommonDir, receipt);
|
|
428
551
|
return { receipt, reusedReceipt: false, snapshotBindingUpgraded: false };
|
|
429
552
|
});
|
|
@@ -434,12 +557,12 @@ function validateAsyncArtifacts(source, artifacts, packet) {
|
|
|
434
557
|
if (expected.length !== actual.length || expected.some((item, index) => item !== actual[index])) {
|
|
435
558
|
fail('async_binding_mismatch');
|
|
436
559
|
}
|
|
560
|
+
const actualDescriptors = [];
|
|
437
561
|
for (const artifact of artifacts) {
|
|
438
562
|
const bytes = Buffer.from(artifact.bytesBase64, 'base64');
|
|
439
563
|
if (createHash('sha256').update(bytes).digest('hex') !== artifact.sha256)
|
|
440
564
|
fail('async_binding_mismatch');
|
|
441
565
|
if (packet) {
|
|
442
|
-
const descriptor = packet.retainedAsync.find(item => item.sha256 === artifact.sha256);
|
|
443
566
|
let row;
|
|
444
567
|
try {
|
|
445
568
|
row = JSON.parse(bytes.toString('utf8'));
|
|
@@ -447,9 +570,15 @@ function validateAsyncArtifacts(source, artifacts, packet) {
|
|
|
447
570
|
catch {
|
|
448
571
|
fail('async_binding_mismatch');
|
|
449
572
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
573
|
+
actualDescriptors.push(JSON.stringify([artifact.sha256, row.model, row.role, row.provider]));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (packet) {
|
|
577
|
+
const expectedDescriptors = packet.retainedAsync.map(item => JSON.stringify([item.sha256, item.model, item.role, item.provider])).sort();
|
|
578
|
+
actualDescriptors.sort();
|
|
579
|
+
if (expectedDescriptors.length !== actualDescriptors.length ||
|
|
580
|
+
expectedDescriptors.some((item, index) => item !== actualDescriptors[index])) {
|
|
581
|
+
fail('async_binding_mismatch');
|
|
453
582
|
}
|
|
454
583
|
}
|
|
455
584
|
}
|
|
@@ -481,23 +610,26 @@ export async function previewOrdinaryPendingLaunch(input) {
|
|
|
481
610
|
}
|
|
482
611
|
if ((options.ownerAlive ?? defaultOwnerAlive)(current.pid))
|
|
483
612
|
fail('owner_alive');
|
|
484
|
-
const packet = validateOrdinaryPendingPackage(options.migrationPackage,
|
|
485
|
-
|
|
486
|
-
baseSha: options.baseSha, attempt: current.attempt, round: current.round, pid: current.pid,
|
|
487
|
-
retainedAsyncSha256: options.retainedAsyncSha256,
|
|
488
|
-
});
|
|
613
|
+
const packet = validateOrdinaryPendingPackage(options.migrationPackage, packageExpected(state, attempts, current, options));
|
|
614
|
+
await assertCycleRecovery(options, state, attempts, packet);
|
|
489
615
|
const [nativeBytes, attemptBytes] = await Promise.all([
|
|
490
616
|
readStable(convergeRunStatePath(options.gitCommonDir, options.target)),
|
|
491
617
|
readStable(convergeAttemptStatePath(options.gitCommonDir, options.target)),
|
|
492
618
|
]);
|
|
493
|
-
const
|
|
619
|
+
const commonSource = { target: options.target,
|
|
494
620
|
headSha: current.headSha, inputSha256: current.inputSha256, pendingAttempt: current.attempt,
|
|
495
621
|
round: current.round, originalPid: current.pid, startedAt: current.startedAt,
|
|
496
622
|
blockingOutcome: 'unknown', reason: 'coordinator_exited_without_durable_blocking_receipts',
|
|
497
623
|
nativeStateSha256: nativeBytes.sha256, attemptStateSha256: attemptBytes.sha256,
|
|
498
624
|
retainedAsyncSha256: options.retainedAsyncSha256,
|
|
499
625
|
migrationPackageSha256: createHash('sha256').update(stableStringify(packet)).digest('hex'),
|
|
500
|
-
}
|
|
626
|
+
};
|
|
627
|
+
const source = packet.version === 2
|
|
628
|
+
? createCycleMigrationSource({ version: 2, ...commonSource, cycle: state.cycle,
|
|
629
|
+
cycleId: state.cycle.id, operationId: state.cycle.operationId,
|
|
630
|
+
attemptCap: attempts.cap, roundCap: state.roundCap, attemptsUsed: attempts.attemptsUsed,
|
|
631
|
+
asyncAttribution: 'cycle-history-unattributed' })
|
|
632
|
+
: createOrdinaryMigrationSource({ version: 1, ...commonSource });
|
|
501
633
|
validateAsyncArtifacts(source, await options.loadRetainedAsync(), packet);
|
|
502
634
|
return { source, nativeStateSha256: nativeBytes.sha256, attemptStateSha256: attemptBytes.sha256,
|
|
503
635
|
attemptsUsed: attempts.attemptsUsed, cap: attempts.cap, nextAttempt: current.attempt + 1 };
|