@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.
Files changed (75) hide show
  1. package/README.md +12 -3
  2. package/dist/converge/attempt-budget.d.ts +14 -0
  3. package/dist/converge/attempt-budget.js +216 -1
  4. package/dist/converge/attempt-budget.js.map +1 -1
  5. package/dist/converge/historical-delivery-reconciliation-schema.d.ts +131 -0
  6. package/dist/converge/historical-delivery-reconciliation-schema.js +111 -0
  7. package/dist/converge/historical-delivery-reconciliation-schema.js.map +1 -0
  8. package/dist/converge/historical-delivery-reconciliation.d.ts +15 -0
  9. package/dist/converge/historical-delivery-reconciliation.js +326 -0
  10. package/dist/converge/historical-delivery-reconciliation.js.map +1 -0
  11. package/dist/converge/launch-guard.js +25 -6
  12. package/dist/converge/launch-guard.js.map +1 -1
  13. package/dist/converge/launch-record.d.ts +36 -0
  14. package/dist/converge/launch-record.js +9 -3
  15. package/dist/converge/launch-record.js.map +1 -1
  16. package/dist/converge/legacy-launch-health.d.ts +36 -0
  17. package/dist/converge/native-lock.d.ts +3 -1
  18. package/dist/converge/native-lock.js +4 -4
  19. package/dist/converge/native-lock.js.map +1 -1
  20. package/dist/converge/ordinary-pending-export.d.ts +3 -0
  21. package/dist/converge/ordinary-pending-export.js +64 -17
  22. package/dist/converge/ordinary-pending-export.js.map +1 -1
  23. package/dist/converge/ordinary-pending-package.d.ts +11 -0
  24. package/dist/converge/ordinary-pending-package.js +18 -4
  25. package/dist/converge/ordinary-pending-package.js.map +1 -1
  26. package/dist/converge/pending-finalization-receipt.d.ts +4 -0
  27. package/dist/converge/pending-finalization-receipt.js +102 -0
  28. package/dist/converge/pending-finalization-receipt.js.map +1 -0
  29. package/dist/converge/pending-legacy-resume.d.ts +42 -3
  30. package/dist/converge/pending-legacy-resume.js +164 -32
  31. package/dist/converge/pending-legacy-resume.js.map +1 -1
  32. package/dist/converge/pending-recovery-source.d.ts +114 -0
  33. package/dist/converge/pending-recovery-source.js +36 -1
  34. package/dist/converge/pending-recovery-source.js.map +1 -1
  35. package/dist/converge/process-identity.d.ts +51 -0
  36. package/dist/converge/process-identity.js +190 -0
  37. package/dist/converge/process-identity.js.map +1 -0
  38. package/dist/converge/recovery-launch.js +39 -17
  39. package/dist/converge/recovery-launch.js.map +1 -1
  40. package/dist/converge/recovery-state.d.ts +55 -0
  41. package/dist/converge/recovery-state.js +306 -0
  42. package/dist/converge/recovery-state.js.map +1 -0
  43. package/dist/converge/run-state.d.ts +43 -1
  44. package/dist/converge/run-state.js +252 -18
  45. package/dist/converge/run-state.js.map +1 -1
  46. package/dist/converge/semantic-state.d.ts +55 -0
  47. package/dist/converge/semantic-state.js +666 -0
  48. package/dist/converge/semantic-state.js.map +1 -0
  49. package/dist/converge/stale-report.d.ts +412 -0
  50. package/dist/converge/stale-report.js +42 -0
  51. package/dist/converge/stale-report.js.map +1 -1
  52. package/dist/converge/terminal-rejection.d.ts +1 -0
  53. package/dist/converge/terminal-rejection.js +6 -6
  54. package/dist/converge/terminal-rejection.js.map +1 -1
  55. package/dist/dispatch/async-lane.d.ts +12 -0
  56. package/dist/dispatch/async-lane.js +94 -0
  57. package/dist/dispatch/async-lane.js.map +1 -1
  58. package/dist/evidence/claim-recovery/validation/native-state.d.ts +22 -0
  59. package/dist/evidence/claim-recovery/validation/native-state.js +91 -9
  60. package/dist/evidence/claim-recovery/validation/native-state.js.map +1 -1
  61. package/dist/evidence/claim-recovery/validation/obligations.js +2 -1
  62. package/dist/evidence/claim-recovery/validation/obligations.js.map +1 -1
  63. package/dist/evidence/claim-recovery/validation/semantic-validation.d.ts +11 -0
  64. package/dist/evidence/claim-recovery/validation/semantic-validation.js +105 -28
  65. package/dist/evidence/claim-recovery/validation/semantic-validation.js.map +1 -1
  66. package/dist/evidence/claim-recovery/validation/types.d.ts +10 -0
  67. package/dist/evidence/original-run/lock-scope.d.ts +12 -6
  68. package/dist/evidence/original-run/lock-scope.js +12 -9
  69. package/dist/evidence/original-run/lock-scope.js.map +1 -1
  70. package/dist/index.js +74 -11
  71. package/dist/index.js.map +1 -1
  72. package/dist/telemetry/recovery/files.d.ts +4 -0
  73. package/dist/telemetry/recovery/files.js +15 -5
  74. package/dist/telemetry/recovery/files.js.map +1 -1
  75. 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
- if (!value || 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
+ 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
- if (!structuredInput || !/^[a-f0-9]{40}$/.test(value.headSha) || !new Set(value.retainedAsyncSha256).size ||
24
- new Set(value.retainedAsyncSha256).size !== value.retainedAsyncSha256.length ||
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":"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"}
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 { AsyncResultReference } from '../dispatch/async-lane.js';
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 ordinaryPendingFinalizationReceiptSchema = ordinaryPendingFinalizationReceiptBodySchema.extend({
45
- receiptDigest: digestSchema,
46
- }).strict().superRefine((receipt, context) => {
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
- new Set(receipt.retainedAsyncSha256).size !== receipt.retainedAsyncSha256.length) {
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 source = createOrdinaryMigrationSource({ version: 1, target, headSha: launch.headSha,
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.length === 0 || hashes.some(value => !/^[a-f0-9]{64}$/.test(value)) ||
124
- new Set(hashes).size !== hashes.length || typeof input.validate !== 'function' ||
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.length === 0 || hashes.some(value => !/^[a-f0-9]{64}$/.test(value)) ||
143
- new Set(hashes).size !== hashes.length || typeof input.loadRetainedAsync !== 'function' ||
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 = ordinaryPendingFinalizationReceiptBodySchema.parse(input);
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
- target: options.target, headSha: current.headSha, inputSha256: current.inputSha256,
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
- source = createOrdinaryMigrationSource({ version: 1, target: options.target,
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 receipt = createFinalizationReceipt({ version: 1, operation: 'ordinary-pending-finalize-only',
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
- if (!descriptor || row.model !== descriptor.model || row.role !== descriptor.role ||
451
- row.provider !== descriptor.provider)
452
- fail('async_binding_mismatch');
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
- target: options.target, headSha: current.headSha, inputSha256: current.inputSha256,
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 source = createOrdinaryMigrationSource({ version: 1, target: options.target,
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 };