@allocator-one/rcl 4.5.3 → 4.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- 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 +2 -0
- package/dist/converge/launch-guard.js.map +1 -1
- package/dist/converge/run-state.d.ts +4 -0
- package/dist/converge/run-state.js +6 -0
- package/dist/converge/run-state.js.map +1 -1
- package/dist/converge/stale-report.d.ts +376 -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/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -437,6 +437,7 @@ commands are documented in
|
|
|
437
437
|
| --- | --- |
|
|
438
438
|
| `rcl converge-report` | Dedupe a round report against earlier rounds, enforce the round cap, classify findings (`new` / `repeat` / `suppressed` / `regating`); refuses inconclusive reviewer health |
|
|
439
439
|
| `rcl converge-verdict` | Record fixed/dismissed verdicts and report the round's resolution |
|
|
440
|
+
| `rcl converge-reconcile-history` | Authenticate and append a reconciliation receipt for an exact historical guarded launch after `lastLaunch` advanced |
|
|
440
441
|
| `rcl converge-stale` | Audited disposition of a healthy or reconciled delivered-hard-failure report that became stale before admission |
|
|
441
442
|
| `rcl converge-gap` | Audited record of one evidenced missing terminal report |
|
|
442
443
|
| `rcl converge-rejected` | Audited disposition of a report rejected locally before delivery |
|
|
@@ -452,6 +453,14 @@ verifiable and can be
|
|
|
452
453
|
followed by a changed-input version 3 disposition only after that exact marker
|
|
453
454
|
upgrade.
|
|
454
455
|
|
|
456
|
+
If a published version 1 marker belongs to a retained version 2 stale receipt
|
|
457
|
+
but a later launch has already replaced `lastLaunch`, use
|
|
458
|
+
`converge-reconcile-history`. Preview prints one reviewed manifest to stdout and
|
|
459
|
+
its SHA-256 to stderr. Apply accepts only those pinned bytes, re-reads every
|
|
460
|
+
local source and the complete live server projection under the target lock,
|
|
461
|
+
and appends a historical audit receipt. It never admits findings, claims an
|
|
462
|
+
attempt or calls a reviewer.
|
|
463
|
+
|
|
455
464
|
---
|
|
456
465
|
|
|
457
466
|
## Configuration
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const historicalServerProjectionSchema: z.ZodObject<{
|
|
3
|
+
origin: z.ZodString;
|
|
4
|
+
runId: z.ZodString;
|
|
5
|
+
provenance: z.ZodLiteral<"live">;
|
|
6
|
+
receivedAt: z.ZodString;
|
|
7
|
+
cycleId: z.ZodString;
|
|
8
|
+
repoVerified: z.ZodLiteral<true>;
|
|
9
|
+
isCrossRepository: z.ZodLiteral<false>;
|
|
10
|
+
headVerified: z.ZodString;
|
|
11
|
+
target: z.ZodObject<{
|
|
12
|
+
kind: z.ZodEnum<{
|
|
13
|
+
patch: "patch";
|
|
14
|
+
pr: "pr";
|
|
15
|
+
}>;
|
|
16
|
+
repo: z.ZodString;
|
|
17
|
+
prNumber: z.ZodNumber;
|
|
18
|
+
headSha: z.ZodString;
|
|
19
|
+
baseSha: z.ZodString;
|
|
20
|
+
diffSha256: z.ZodString;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
converge: z.ZodObject<{
|
|
23
|
+
target: z.ZodString;
|
|
24
|
+
attempt: z.ZodNumber;
|
|
25
|
+
round: z.ZodNumber;
|
|
26
|
+
}, z.core.$strict>;
|
|
27
|
+
report: z.ZodObject<{
|
|
28
|
+
declaredSha256: z.ZodString;
|
|
29
|
+
declaredBytes: z.ZodNumber;
|
|
30
|
+
stored: z.ZodLiteral<true>;
|
|
31
|
+
}, z.core.$strict>;
|
|
32
|
+
}, z.core.$strict>;
|
|
33
|
+
export declare const historicalDeliveryReconciliationManifestSchema: z.ZodObject<{
|
|
34
|
+
kind: z.ZodLiteral<"rcl-historical-delivery-reconciliation">;
|
|
35
|
+
version: z.ZodLiteral<1>;
|
|
36
|
+
operationId: z.ZodString;
|
|
37
|
+
createdAt: z.ZodString;
|
|
38
|
+
gitCommonDir: z.ZodString;
|
|
39
|
+
target: z.ZodString;
|
|
40
|
+
runId: z.ZodString;
|
|
41
|
+
nativeStateSha256: z.ZodString;
|
|
42
|
+
attemptStateSha256: z.ZodString;
|
|
43
|
+
sourceStaleManifestSha256s: z.ZodArray<z.ZodString>;
|
|
44
|
+
successor: z.ZodObject<{
|
|
45
|
+
runId: z.ZodString;
|
|
46
|
+
attempt: z.ZodNumber;
|
|
47
|
+
round: z.ZodNumber;
|
|
48
|
+
headSha: z.ZodString;
|
|
49
|
+
inputSha256: z.ZodString;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
reconciliation: z.ZodObject<{
|
|
52
|
+
version: z.ZodLiteral<2>;
|
|
53
|
+
runId: z.ZodString;
|
|
54
|
+
reportJsonSha256: z.ZodString;
|
|
55
|
+
headSha: z.ZodString;
|
|
56
|
+
inputSha256: z.ZodString;
|
|
57
|
+
attempt: z.ZodNumber;
|
|
58
|
+
round: z.ZodNumber;
|
|
59
|
+
claimPid: z.ZodNumber;
|
|
60
|
+
cycleId: z.ZodNullable<z.ZodString>;
|
|
61
|
+
reconciledAt: z.ZodString;
|
|
62
|
+
}, z.core.$strict>;
|
|
63
|
+
baseSha: z.ZodString;
|
|
64
|
+
diffSha256: z.ZodString;
|
|
65
|
+
reviewerHealth: z.ZodObject<{
|
|
66
|
+
version: z.ZodLiteral<1>;
|
|
67
|
+
policy: z.ZodObject<{
|
|
68
|
+
version: z.ZodLiteral<1>;
|
|
69
|
+
fraction: z.ZodNumber;
|
|
70
|
+
seatCount: z.ZodNumber;
|
|
71
|
+
minimumSuccessful: z.ZodNumber;
|
|
72
|
+
}, z.core.$strict>;
|
|
73
|
+
successfulSeats: z.ZodNumber;
|
|
74
|
+
}, z.core.$strict>;
|
|
75
|
+
server: z.ZodObject<{
|
|
76
|
+
origin: z.ZodString;
|
|
77
|
+
runId: z.ZodString;
|
|
78
|
+
provenance: z.ZodLiteral<"live">;
|
|
79
|
+
receivedAt: z.ZodString;
|
|
80
|
+
cycleId: z.ZodString;
|
|
81
|
+
repoVerified: z.ZodLiteral<true>;
|
|
82
|
+
isCrossRepository: z.ZodLiteral<false>;
|
|
83
|
+
headVerified: z.ZodString;
|
|
84
|
+
target: z.ZodObject<{
|
|
85
|
+
kind: z.ZodEnum<{
|
|
86
|
+
patch: "patch";
|
|
87
|
+
pr: "pr";
|
|
88
|
+
}>;
|
|
89
|
+
repo: z.ZodString;
|
|
90
|
+
prNumber: z.ZodNumber;
|
|
91
|
+
headSha: z.ZodString;
|
|
92
|
+
baseSha: z.ZodString;
|
|
93
|
+
diffSha256: z.ZodString;
|
|
94
|
+
}, z.core.$strict>;
|
|
95
|
+
converge: z.ZodObject<{
|
|
96
|
+
target: z.ZodString;
|
|
97
|
+
attempt: z.ZodNumber;
|
|
98
|
+
round: z.ZodNumber;
|
|
99
|
+
}, z.core.$strict>;
|
|
100
|
+
report: z.ZodObject<{
|
|
101
|
+
declaredSha256: z.ZodString;
|
|
102
|
+
declaredBytes: z.ZodNumber;
|
|
103
|
+
stored: z.ZodLiteral<true>;
|
|
104
|
+
}, z.core.$strict>;
|
|
105
|
+
}, z.core.$strict>;
|
|
106
|
+
serverProjectionSha256: z.ZodString;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
export type HistoricalDeliveryReconciliationManifest = z.infer<typeof historicalDeliveryReconciliationManifestSchema>;
|
|
109
|
+
export declare const historicalDeliveryReconciliationReceiptSchema: z.ZodObject<{
|
|
110
|
+
kind: z.ZodLiteral<"rcl-historical-delivery-reconciliation-receipt">;
|
|
111
|
+
version: z.ZodLiteral<1>;
|
|
112
|
+
operationId: z.ZodString;
|
|
113
|
+
runId: z.ZodString;
|
|
114
|
+
manifestSha256: z.ZodString;
|
|
115
|
+
beforeStateSha256: z.ZodString;
|
|
116
|
+
afterStateSha256: z.ZodString;
|
|
117
|
+
attemptStateSha256: z.ZodString;
|
|
118
|
+
sourceStaleManifestSha256s: z.ZodArray<z.ZodString>;
|
|
119
|
+
serverProjectionSha256: z.ZodString;
|
|
120
|
+
}, z.core.$strict>;
|
|
121
|
+
export type HistoricalDeliveryReconciliationReceipt = z.infer<typeof historicalDeliveryReconciliationReceiptSchema>;
|
|
122
|
+
export interface HistoricalDeliveryReconciliationEntry {
|
|
123
|
+
manifestJson: string;
|
|
124
|
+
manifestSha256: string;
|
|
125
|
+
}
|
|
126
|
+
export declare function historicalDeliveryManifest(entry: HistoricalDeliveryReconciliationEntry): HistoricalDeliveryReconciliationManifest;
|
|
127
|
+
export declare function validateHistoricalDeliveryReconciliationAudit(value: {
|
|
128
|
+
target?: string;
|
|
129
|
+
historicalDeliveryReconciliationAudit?: HistoricalDeliveryReconciliationEntry[];
|
|
130
|
+
historicalDeliveryReconciliationAuditCount?: number;
|
|
131
|
+
}): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { reviewerHealthSchema, strongDeliveryReconciliationSchema } from './launch-record.js';
|
|
4
|
+
import { decodeRecoveryDocument } from '../evidence/original-run/decode.js';
|
|
5
|
+
import { serializeRecoveryDocument } from '../evidence/original-run/journal.js';
|
|
6
|
+
import { sha256 } from '../telemetry/recovery/files.js';
|
|
7
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
8
|
+
const commit = z.string().regex(/^[a-f0-9]{40}$/);
|
|
9
|
+
const uuid = z.string().uuid();
|
|
10
|
+
export const historicalServerProjectionSchema = z.object({
|
|
11
|
+
origin: z.string().url(),
|
|
12
|
+
runId: uuid,
|
|
13
|
+
provenance: z.literal('live'),
|
|
14
|
+
receivedAt: z.string().datetime({ offset: true }),
|
|
15
|
+
cycleId: uuid,
|
|
16
|
+
repoVerified: z.literal(true),
|
|
17
|
+
isCrossRepository: z.literal(false),
|
|
18
|
+
headVerified: z.string().min(1),
|
|
19
|
+
target: z.object({
|
|
20
|
+
kind: z.enum(['pr', 'patch']),
|
|
21
|
+
repo: z.string().min(1),
|
|
22
|
+
prNumber: z.number().int().positive().safe(),
|
|
23
|
+
headSha: commit,
|
|
24
|
+
baseSha: commit,
|
|
25
|
+
diffSha256: digest,
|
|
26
|
+
}).strict(),
|
|
27
|
+
converge: z.object({
|
|
28
|
+
target: z.string().min(1),
|
|
29
|
+
attempt: z.number().int().positive().safe(),
|
|
30
|
+
round: z.number().int().positive().safe(),
|
|
31
|
+
}).strict(),
|
|
32
|
+
report: z.object({
|
|
33
|
+
declaredSha256: digest,
|
|
34
|
+
declaredBytes: z.number().int().nonnegative().safe(),
|
|
35
|
+
stored: z.literal(true),
|
|
36
|
+
}).strict(),
|
|
37
|
+
}).strict();
|
|
38
|
+
export const historicalDeliveryReconciliationManifestSchema = z.object({
|
|
39
|
+
kind: z.literal('rcl-historical-delivery-reconciliation'),
|
|
40
|
+
version: z.literal(1),
|
|
41
|
+
operationId: uuid,
|
|
42
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
43
|
+
gitCommonDir: z.string().min(1),
|
|
44
|
+
target: z.string().min(1),
|
|
45
|
+
runId: uuid,
|
|
46
|
+
nativeStateSha256: digest,
|
|
47
|
+
attemptStateSha256: digest,
|
|
48
|
+
sourceStaleManifestSha256s: z.array(digest).min(1).max(100).refine(values => new Set(values).size === values.length && isDeepStrictEqual(values, [...values].sort()), 'Historical stale manifest digests must be unique and sorted'),
|
|
49
|
+
successor: z.object({
|
|
50
|
+
runId: uuid,
|
|
51
|
+
attempt: z.number().int().positive().safe(),
|
|
52
|
+
round: z.number().int().positive().safe(),
|
|
53
|
+
headSha: commit,
|
|
54
|
+
inputSha256: digest,
|
|
55
|
+
}).strict(),
|
|
56
|
+
reconciliation: strongDeliveryReconciliationSchema,
|
|
57
|
+
baseSha: commit,
|
|
58
|
+
diffSha256: digest,
|
|
59
|
+
reviewerHealth: reviewerHealthSchema,
|
|
60
|
+
server: historicalServerProjectionSchema,
|
|
61
|
+
serverProjectionSha256: digest,
|
|
62
|
+
}).strict();
|
|
63
|
+
export const historicalDeliveryReconciliationReceiptSchema = z.object({
|
|
64
|
+
kind: z.literal('rcl-historical-delivery-reconciliation-receipt'),
|
|
65
|
+
version: z.literal(1),
|
|
66
|
+
operationId: uuid,
|
|
67
|
+
runId: uuid,
|
|
68
|
+
manifestSha256: digest,
|
|
69
|
+
beforeStateSha256: digest,
|
|
70
|
+
afterStateSha256: digest,
|
|
71
|
+
attemptStateSha256: digest,
|
|
72
|
+
sourceStaleManifestSha256s: z.array(digest).min(1).max(100),
|
|
73
|
+
serverProjectionSha256: digest,
|
|
74
|
+
}).strict();
|
|
75
|
+
const historicalDeliveryReconciliationEntrySchema = z.object({
|
|
76
|
+
manifestJson: z.string().max(128 * 1024), manifestSha256: digest,
|
|
77
|
+
}).strict();
|
|
78
|
+
export function historicalDeliveryManifest(entry) {
|
|
79
|
+
const parsedEntry = historicalDeliveryReconciliationEntrySchema.parse(entry);
|
|
80
|
+
if (sha256(parsedEntry.manifestJson) !== parsedEntry.manifestSha256)
|
|
81
|
+
throw new Error('historical_delivery_reconciliation_manifest_digest_mismatch');
|
|
82
|
+
const manifest = historicalDeliveryReconciliationManifestSchema.parse(decodeRecoveryDocument(parsedEntry.manifestJson));
|
|
83
|
+
if (parsedEntry.manifestJson !== serializeRecoveryDocument(manifest)) {
|
|
84
|
+
throw new Error('historical_delivery_reconciliation_manifest_noncanonical');
|
|
85
|
+
}
|
|
86
|
+
return manifest;
|
|
87
|
+
}
|
|
88
|
+
export function validateHistoricalDeliveryReconciliationAudit(value) {
|
|
89
|
+
const audit = value.historicalDeliveryReconciliationAudit;
|
|
90
|
+
if (audit === undefined) {
|
|
91
|
+
if (value.historicalDeliveryReconciliationAuditCount !== undefined) {
|
|
92
|
+
throw new Error('historical_delivery_reconciliation_audit_count_mismatch');
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(audit) || audit.length === 0 || audit.length > 10_000 ||
|
|
97
|
+
value.historicalDeliveryReconciliationAuditCount !== audit.length) {
|
|
98
|
+
throw new Error('historical_delivery_reconciliation_audit_count_mismatch');
|
|
99
|
+
}
|
|
100
|
+
const operations = new Set();
|
|
101
|
+
const runs = new Set();
|
|
102
|
+
for (const entry of audit) {
|
|
103
|
+
const manifest = historicalDeliveryManifest(entry);
|
|
104
|
+
if (manifest.target !== value.target || operations.has(manifest.operationId) || runs.has(manifest.runId)) {
|
|
105
|
+
throw new Error('historical_delivery_reconciliation_audit_duplicate');
|
|
106
|
+
}
|
|
107
|
+
operations.add(manifest.operationId);
|
|
108
|
+
runs.add(manifest.runId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=historical-delivery-reconciliation-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"historical-delivery-reconciliation-schema.js","sourceRoot":"","sources":["../../src/converge/historical-delivery-reconciliation-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HistoricalDeliveryReconciliationManifest } from './historical-delivery-reconciliation-schema.js';
|
|
2
|
+
import { type ConvergeRunState } from './run-state.js';
|
|
3
|
+
import { getRun } from '../evidence/reads.js';
|
|
4
|
+
import type { HarnessSink } from '../telemetry/sink.js';
|
|
5
|
+
interface HistoricalReconciliationOptions {
|
|
6
|
+
getRun?: typeof getRun;
|
|
7
|
+
now?: () => Date;
|
|
8
|
+
}
|
|
9
|
+
/** Print-only qualification; callers may write the returned reviewed manifest separately. */
|
|
10
|
+
export declare function previewHistoricalDeliveryReconciliation(input: unknown, gitCommonDir: string, sink: HarnessSink, options?: HistoricalReconciliationOptions): Promise<HistoricalDeliveryReconciliationManifest>;
|
|
11
|
+
/** Reverify every authority source under the target lock, then append one local receipt atomically. */
|
|
12
|
+
export declare function applyHistoricalDeliveryReconciliation(input: unknown, gitCommonDir: string, sink: HarnessSink, options?: HistoricalReconciliationOptions): Promise<'applied' | 'unchanged'>;
|
|
13
|
+
/** Local fail-closed verification used before claims and report admission. */
|
|
14
|
+
export declare function verifyHistoricalDeliveryReconciliations(common: string, state: ConvergeRunState): Promise<void>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { realpath } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { convergeAttemptStatePath, validateConvergeAttemptState } from './attempt-budget.js';
|
|
7
|
+
import { assertNoPendingFreshReview } from './fresh-review.js';
|
|
8
|
+
import { historicalDeliveryManifest, historicalDeliveryReconciliationManifestSchema, historicalDeliveryReconciliationReceiptSchema, historicalServerProjectionSchema, validateHistoricalDeliveryReconciliationAudit, } from './historical-delivery-reconciliation-schema.js';
|
|
9
|
+
import { strongDeliveryReconciliationSchema } from './launch-record.js';
|
|
10
|
+
import { convergeRunStatePath, loadConvergeRunStateEvidence, writeState, } from './run-state.js';
|
|
11
|
+
import { inspectVerifiedStaleReportReceipts } from './stale-report.js';
|
|
12
|
+
import { readStaleObject, retainStaleFile as retain, selectedStaleFile as selected } from './stale-report-storage.js';
|
|
13
|
+
import { assertDeadOwner } from './terminal-rejection.js';
|
|
14
|
+
import { withRecoveryTarget } from './target-ownership.js';
|
|
15
|
+
import { getRun } from '../evidence/reads.js';
|
|
16
|
+
import { decodeRecoveryDocument } from '../evidence/original-run/decode.js';
|
|
17
|
+
import { serializeRecoveryDocument, syncDirectory } from '../evidence/original-run/journal.js';
|
|
18
|
+
import { inspectRecoveryDirectory, prepareLockRoot } from '../evidence/original-run/lock-path.js';
|
|
19
|
+
import { normalizeUrl } from '../telemetry/credentials.js';
|
|
20
|
+
import { readStable, sha256 } from '../telemetry/recovery/files.js';
|
|
21
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
|
22
|
+
const selectionSchema = z.object({
|
|
23
|
+
target: z.string().min(1).max(200).refine(value => value.trim() === value),
|
|
24
|
+
runId: z.string().uuid(),
|
|
25
|
+
}).strict();
|
|
26
|
+
const applySchema = z.object({ manifest: z.string().min(1), manifestSha256: digest }).strict();
|
|
27
|
+
function fail(code) { throw new Error(`historical_delivery_reconciliation_${code}`); }
|
|
28
|
+
function endpoint(value) {
|
|
29
|
+
const normalized = normalizeUrl(value);
|
|
30
|
+
if (!normalized)
|
|
31
|
+
fail('origin_invalid');
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function selectedServerProjection(sink, detail, local) {
|
|
35
|
+
const cycle = local.state.cycle;
|
|
36
|
+
if (!cycle || endpoint(sink.baseUrl) !== endpoint(cycle.url))
|
|
37
|
+
fail('server_origin_mismatch');
|
|
38
|
+
const reports = detail.artifacts?.filter(artifact => artifact.kind === 'report_json') ?? [];
|
|
39
|
+
const report = reports[0];
|
|
40
|
+
if (detail.id.toLowerCase() !== local.runId.toLowerCase() || detail.provenance !== 'live' ||
|
|
41
|
+
detail.repo_verified !== true || detail.is_cross_repository !== false ||
|
|
42
|
+
typeof detail.head_verified !== 'string' || detail.head_verified.length === 0 ||
|
|
43
|
+
detail.cycle_id !== cycle.id || reports.length !== 1 || !report ||
|
|
44
|
+
report.stored !== true || report.declared_sha256 !== local.reportSha256 ||
|
|
45
|
+
report.declared_bytes !== local.reportBytes || detail.target.kind !== local.targetKind ||
|
|
46
|
+
detail.target.repo?.toLowerCase() !== cycle.repo.toLowerCase() || detail.target.pr_number !== cycle.prNumber ||
|
|
47
|
+
detail.target.head_sha !== local.headSha || detail.target.base_sha !== local.baseSha ||
|
|
48
|
+
detail.target.diff_sha256 !== local.diffSha256 ||
|
|
49
|
+
detail.converge?.target !== local.target || detail.converge.attempt !== local.attempt ||
|
|
50
|
+
detail.converge.round !== local.round || typeof detail.received_at !== 'string') {
|
|
51
|
+
fail('server_binding_mismatch');
|
|
52
|
+
}
|
|
53
|
+
const parsed = z.string().datetime({ offset: true }).safeParse(detail.received_at);
|
|
54
|
+
if (!parsed.success)
|
|
55
|
+
fail('server_binding_mismatch');
|
|
56
|
+
return historicalServerProjectionSchema.parse({
|
|
57
|
+
origin: endpoint(sink.baseUrl), runId: detail.id, provenance: 'live', receivedAt: detail.received_at,
|
|
58
|
+
cycleId: cycle.id, repoVerified: true, isCrossRepository: false,
|
|
59
|
+
headVerified: detail.head_verified,
|
|
60
|
+
target: { kind: detail.target.kind, repo: detail.target.repo, prNumber: detail.target.pr_number,
|
|
61
|
+
headSha: detail.target.head_sha, baseSha: detail.target.base_sha, diffSha256: detail.target.diff_sha256 },
|
|
62
|
+
converge: { target: detail.converge.target, attempt: detail.converge.attempt, round: detail.converge.round },
|
|
63
|
+
report: { declaredSha256: report.declared_sha256, declaredBytes: report.declared_bytes, stored: true },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function readServerProjection(sink, local, options) {
|
|
67
|
+
const outcome = await (options.getRun ?? getRun)(sink, local.runId, { requireCompleteRead: true });
|
|
68
|
+
if (outcome.kind !== 'ok')
|
|
69
|
+
fail(`server_${outcome.kind}`);
|
|
70
|
+
return selectedServerProjection(sink, outcome.value, local);
|
|
71
|
+
}
|
|
72
|
+
function commonPredecessor(proofs, target, runId) {
|
|
73
|
+
const matches = proofs.filter((proof) => proof.m.version === 2 &&
|
|
74
|
+
proof.m.outcome === 'delivered-hard-failure' && proof.m.target === target &&
|
|
75
|
+
proof.m.runId.toLowerCase() === runId.toLowerCase());
|
|
76
|
+
if (matches.length === 0)
|
|
77
|
+
fail('predecessor_not_found');
|
|
78
|
+
const first = matches[0];
|
|
79
|
+
const launch = first.previous;
|
|
80
|
+
const marker = launch.deliveryReconciliation;
|
|
81
|
+
if (marker?.version !== 1 || launch.status !== 'completed' || launch.hardFailure !== true ||
|
|
82
|
+
launch.deliveryPending !== false || launch.exitCode !== 4 || !launch.reviewerHealth ||
|
|
83
|
+
launch.runId !== runId || launch.reportJsonSha256 !== first.m.reportSha256 ||
|
|
84
|
+
first.m.deliveryReconciliation.version !== 1 || !isDeepStrictEqual(first.m.deliveryReconciliation, marker)) {
|
|
85
|
+
fail('predecessor_ineligible');
|
|
86
|
+
}
|
|
87
|
+
const binding = {
|
|
88
|
+
target: first.m.target, runId: first.m.runId, reportSha256: first.m.reportSha256,
|
|
89
|
+
headSha: first.m.previousHeadSha, inputSha256: first.m.previousInputSha256,
|
|
90
|
+
attempt: first.m.attempt, round: first.m.round, cycleId: first.m.cycleId,
|
|
91
|
+
marker: first.m.deliveryReconciliation, reviewerHealth: first.m.reviewerHealth,
|
|
92
|
+
launch, evidence: first.evidence,
|
|
93
|
+
};
|
|
94
|
+
for (const proof of matches.slice(1)) {
|
|
95
|
+
const candidate = {
|
|
96
|
+
target: proof.m.target, runId: proof.m.runId, reportSha256: proof.m.reportSha256,
|
|
97
|
+
headSha: proof.m.previousHeadSha, inputSha256: proof.m.previousInputSha256,
|
|
98
|
+
attempt: proof.m.attempt, round: proof.m.round, cycleId: proof.m.cycleId,
|
|
99
|
+
marker: proof.m.deliveryReconciliation, reviewerHealth: proof.m.reviewerHealth,
|
|
100
|
+
launch: proof.previous, evidence: proof.evidence,
|
|
101
|
+
};
|
|
102
|
+
if (!isDeepStrictEqual(candidate, binding))
|
|
103
|
+
fail('predecessor_ambiguous');
|
|
104
|
+
}
|
|
105
|
+
return { binding, sourceDigests: matches.map(proof => proof.entry.manifestSha256).sort() };
|
|
106
|
+
}
|
|
107
|
+
async function deriveLocalAuthority(common, target, runId, state, nativeSha256, attemptFile, requireDeadOwner) {
|
|
108
|
+
validateHistoricalDeliveryReconciliationAudit(state);
|
|
109
|
+
if (!state.cycle)
|
|
110
|
+
fail('cycle_missing');
|
|
111
|
+
const attemptsPath = convergeAttemptStatePath(common, target);
|
|
112
|
+
const attempts = validateConvergeAttemptState(decodeRecoveryDocument(attemptFile.text), target, attemptsPath);
|
|
113
|
+
if (!isDeepStrictEqual(attempts.cycle, state.cycle))
|
|
114
|
+
fail('cycle_mismatch');
|
|
115
|
+
const proofs = await inspectVerifiedStaleReportReceipts(common, state.staleReportAudit ?? []);
|
|
116
|
+
const { binding, sourceDigests } = commonPredecessor(proofs, target, runId);
|
|
117
|
+
if (binding.cycleId !== state.cycle.id || !isDeepStrictEqual(binding.evidence.attempts.cycle, state.cycle)) {
|
|
118
|
+
fail('cycle_mismatch');
|
|
119
|
+
}
|
|
120
|
+
const claim = attempts.attempts.find(record => record.attempt === binding.attempt && record.source === 'claim');
|
|
121
|
+
if (!claim || claim.pid !== binding.launch.pid || binding.launch.pid !== binding.evidence.attempts.attempts
|
|
122
|
+
.find(record => record.attempt === binding.attempt && record.source === 'claim')?.pid)
|
|
123
|
+
fail('attempt_mismatch');
|
|
124
|
+
if (requireDeadOwner)
|
|
125
|
+
assertDeadOwner(binding.launch.pid);
|
|
126
|
+
const reportTarget = binding.evidence.report.run.target;
|
|
127
|
+
if (!['pr', 'patch'].includes(reportTarget.kind))
|
|
128
|
+
fail('retained_report_target_kind_mismatch');
|
|
129
|
+
if (reportTarget.repo?.toLowerCase() !== state.cycle.repo.toLowerCase())
|
|
130
|
+
fail('retained_report_repository_mismatch');
|
|
131
|
+
if (reportTarget.pr_number !== state.cycle.prNumber)
|
|
132
|
+
fail('retained_report_pull_request_mismatch');
|
|
133
|
+
if (reportTarget.head_sha !== binding.headSha)
|
|
134
|
+
fail('retained_report_head_mismatch');
|
|
135
|
+
if (!/^[a-f0-9]{40}$/.test(reportTarget.base_sha ?? ''))
|
|
136
|
+
fail('retained_report_base_missing');
|
|
137
|
+
if (!/^[a-f0-9]{64}$/.test(reportTarget.diff_sha256 ?? ''))
|
|
138
|
+
fail('retained_report_diff_missing');
|
|
139
|
+
const retainedReport = await readStaleObject(common, binding.reportSha256);
|
|
140
|
+
const successor = state.lastLaunch;
|
|
141
|
+
if (!successor?.runId || successor.status !== 'completed' || successor.attempt <= binding.attempt ||
|
|
142
|
+
successor.runId === binding.runId)
|
|
143
|
+
fail('successor_missing');
|
|
144
|
+
const successorClaim = attempts.attempts.find(record => record.attempt === successor.attempt && record.source === 'claim');
|
|
145
|
+
if (attempts.attemptsUsed !== successor.attempt || !successorClaim || successorClaim.pid !== successor.pid) {
|
|
146
|
+
fail('successor_attempt_mismatch');
|
|
147
|
+
}
|
|
148
|
+
const reconciliation = strongDeliveryReconciliationSchema.parse({ version: 2, runId: binding.runId,
|
|
149
|
+
reportJsonSha256: binding.reportSha256, headSha: binding.headSha, inputSha256: binding.inputSha256,
|
|
150
|
+
attempt: binding.attempt, round: binding.round, claimPid: binding.launch.pid, cycleId: state.cycle.id,
|
|
151
|
+
reconciledAt: '1970-01-01T00:00:00.000Z' });
|
|
152
|
+
return { common, target, runId, state, nativeSha256, attempts, attemptFile, attemptSha256: attemptFile.sha256,
|
|
153
|
+
sourceDigests, headSha: binding.headSha, reportSha256: binding.reportSha256, attempt: binding.attempt,
|
|
154
|
+
round: binding.round, reviewerHealth: binding.reviewerHealth, reconciliation,
|
|
155
|
+
targetKind: reportTarget.kind, baseSha: reportTarget.base_sha,
|
|
156
|
+
diffSha256: reportTarget.diff_sha256, reportBytes: retainedReport.raw.byteLength,
|
|
157
|
+
successor: { runId: successor.runId, attempt: successor.attempt, round: successor.round,
|
|
158
|
+
headSha: successor.headSha, inputSha256: successor.inputSha256 } };
|
|
159
|
+
}
|
|
160
|
+
async function localAuthority(common, target, runId) {
|
|
161
|
+
await assertNoPendingFreshReview(common, target);
|
|
162
|
+
const native = await loadConvergeRunStateEvidence(common, target);
|
|
163
|
+
if (!native)
|
|
164
|
+
fail('state_missing');
|
|
165
|
+
if (native.state.historicalDeliveryReconciliationAudit?.some(entry => historicalDeliveryManifest(entry).runId === runId))
|
|
166
|
+
fail('already_applied');
|
|
167
|
+
const attemptFile = await readStable(convergeAttemptStatePath(common, target));
|
|
168
|
+
return deriveLocalAuthority(common, target, runId, native.state, native.sha256, attemptFile, true);
|
|
169
|
+
}
|
|
170
|
+
function assertServerBinding(local, server) {
|
|
171
|
+
const cycle = local.state.cycle;
|
|
172
|
+
if (!cycle || server.origin !== endpoint(cycle.url) || server.runId.toLowerCase() !== local.runId.toLowerCase() ||
|
|
173
|
+
server.cycleId !== cycle.id || server.target.kind !== local.targetKind ||
|
|
174
|
+
server.target.repo.toLowerCase() !== cycle.repo.toLowerCase() || server.target.prNumber !== cycle.prNumber ||
|
|
175
|
+
server.target.headSha !== local.headSha || server.target.baseSha !== local.baseSha ||
|
|
176
|
+
server.target.diffSha256 !== local.diffSha256 || server.converge.target !== local.target ||
|
|
177
|
+
server.converge.attempt !== local.attempt || server.converge.round !== local.round ||
|
|
178
|
+
server.report.declaredSha256 !== local.reportSha256 || server.report.declaredBytes !== local.reportBytes) {
|
|
179
|
+
fail('retained_server_binding_mismatch');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function buildManifest(local, server, operationId, createdAt) {
|
|
183
|
+
assertServerBinding(local, server);
|
|
184
|
+
return historicalDeliveryReconciliationManifestSchema.parse({
|
|
185
|
+
kind: 'rcl-historical-delivery-reconciliation', version: 1, operationId, createdAt,
|
|
186
|
+
gitCommonDir: local.common, target: local.target, runId: local.runId,
|
|
187
|
+
nativeStateSha256: local.nativeSha256, attemptStateSha256: local.attemptSha256,
|
|
188
|
+
sourceStaleManifestSha256s: local.sourceDigests, successor: local.successor,
|
|
189
|
+
reconciliation: { ...local.reconciliation, reconciledAt: createdAt },
|
|
190
|
+
baseSha: server.target.baseSha, diffSha256: server.target.diffSha256,
|
|
191
|
+
reviewerHealth: local.reviewerHealth, server,
|
|
192
|
+
serverProjectionSha256: sha256(Buffer.from(serializeRecoveryDocument(server))),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/** Print-only qualification; callers may write the returned reviewed manifest separately. */
|
|
196
|
+
export async function previewHistoricalDeliveryReconciliation(input, gitCommonDir, sink, options = {}) {
|
|
197
|
+
const selection = selectionSchema.parse(input);
|
|
198
|
+
const common = await realpath(resolve(gitCommonDir));
|
|
199
|
+
const local = await localAuthority(common, selection.target, selection.runId);
|
|
200
|
+
const server = await readServerProjection(sink, local, options);
|
|
201
|
+
return buildManifest(local, server, randomUUID(), (options.now ?? (() => new Date()))().toISOString());
|
|
202
|
+
}
|
|
203
|
+
function nextState(before, entry, at) {
|
|
204
|
+
const { staleReportAudit, staleReportAuditCount, historicalDeliveryReconciliationAudit: audit = [], historicalDeliveryReconciliationAuditCount: _historicalCount, updatedAt: _updatedAt, ...rest } = before;
|
|
205
|
+
return {
|
|
206
|
+
...(staleReportAudit ? { staleReportAudit } : {}), ...rest,
|
|
207
|
+
historicalDeliveryReconciliationAudit: [...audit, entry],
|
|
208
|
+
historicalDeliveryReconciliationAuditCount: audit.length + 1,
|
|
209
|
+
...(staleReportAuditCount !== undefined ? { staleReportAuditCount } : {}), updatedAt: at,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function directory(common, manifest) {
|
|
213
|
+
return join(common, 'rcl-historical-delivery-reconciliations', manifest.operationId);
|
|
214
|
+
}
|
|
215
|
+
function receiptFor(manifest, manifestSha256, afterStateSha256) {
|
|
216
|
+
return historicalDeliveryReconciliationReceiptSchema.parse({
|
|
217
|
+
kind: 'rcl-historical-delivery-reconciliation-receipt', version: 1,
|
|
218
|
+
operationId: manifest.operationId, runId: manifest.runId, manifestSha256,
|
|
219
|
+
beforeStateSha256: manifest.nativeStateSha256, afterStateSha256,
|
|
220
|
+
attemptStateSha256: manifest.attemptStateSha256,
|
|
221
|
+
sourceStaleManifestSha256s: manifest.sourceStaleManifestSha256s,
|
|
222
|
+
serverProjectionSha256: manifest.serverProjectionSha256,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/** Reverify every authority source under the target lock, then append one local receipt atomically. */
|
|
226
|
+
export async function applyHistoricalDeliveryReconciliation(input, gitCommonDir, sink, options = {}) {
|
|
227
|
+
const pinned = applySchema.parse(input);
|
|
228
|
+
const selectedManifest = await readStable(pinned.manifest, 128 * 1024);
|
|
229
|
+
if (selectedManifest.sha256 !== pinned.manifestSha256)
|
|
230
|
+
fail('manifest_digest_mismatch');
|
|
231
|
+
const manifest = historicalDeliveryReconciliationManifestSchema.parse(decodeRecoveryDocument(selectedManifest.text));
|
|
232
|
+
if (selectedManifest.text !== serializeRecoveryDocument(manifest))
|
|
233
|
+
fail('manifest_noncanonical');
|
|
234
|
+
const common = await realpath(resolve(gitCommonDir));
|
|
235
|
+
if (manifest.gitCommonDir !== common)
|
|
236
|
+
fail('repository_mismatch');
|
|
237
|
+
return withRecoveryTarget(common, manifest.target, async (ownership) => {
|
|
238
|
+
const currentFile = await readStable(convergeRunStatePath(common, manifest.target));
|
|
239
|
+
const current = await loadConvergeRunStateEvidence(common, manifest.target);
|
|
240
|
+
if (!current || current.sha256 !== currentFile.sha256)
|
|
241
|
+
fail('state_missing');
|
|
242
|
+
validateHistoricalDeliveryReconciliationAudit(current.state);
|
|
243
|
+
await verifyHistoricalDeliveryReconciliations(common, current.state);
|
|
244
|
+
const entry = {
|
|
245
|
+
manifestJson: selectedManifest.text, manifestSha256: selectedManifest.sha256,
|
|
246
|
+
};
|
|
247
|
+
const prior = current.state.historicalDeliveryReconciliationAudit?.find(entry => historicalDeliveryManifest(entry).runId === manifest.runId);
|
|
248
|
+
if (prior) {
|
|
249
|
+
if (!isDeepStrictEqual(prior, entry))
|
|
250
|
+
fail('operation_conflict');
|
|
251
|
+
return 'unchanged';
|
|
252
|
+
}
|
|
253
|
+
await assertNoPendingFreshReview(common, manifest.target);
|
|
254
|
+
if (current.sha256 !== manifest.nativeStateSha256)
|
|
255
|
+
fail('state_changed');
|
|
256
|
+
const attemptFile = await readStable(convergeAttemptStatePath(common, manifest.target));
|
|
257
|
+
const local = await deriveLocalAuthority(common, manifest.target, manifest.runId, current.state, current.sha256, attemptFile, true);
|
|
258
|
+
const server = await readServerProjection(sink, local, options);
|
|
259
|
+
assertServerBinding(local, server);
|
|
260
|
+
const expected = buildManifest(local, server, manifest.operationId, manifest.createdAt);
|
|
261
|
+
if (!isDeepStrictEqual(expected, manifest))
|
|
262
|
+
fail('manifest_binding_mismatch');
|
|
263
|
+
const next = nextState(current.state, entry, manifest.createdAt);
|
|
264
|
+
validateHistoricalDeliveryReconciliationAudit(next);
|
|
265
|
+
const afterBytes = Buffer.from(serializeRecoveryDocument(next));
|
|
266
|
+
const serverBytes = Buffer.from(serializeRecoveryDocument(server));
|
|
267
|
+
if (sha256(serverBytes) !== manifest.serverProjectionSha256)
|
|
268
|
+
fail('server_projection_digest_mismatch');
|
|
269
|
+
const receipt = receiptFor(manifest, selectedManifest.sha256, sha256(afterBytes));
|
|
270
|
+
const dir = directory(common, manifest);
|
|
271
|
+
await prepareLockRoot(dirname(dir));
|
|
272
|
+
await syncDirectory(common);
|
|
273
|
+
await prepareLockRoot(dir);
|
|
274
|
+
await syncDirectory(dirname(dir));
|
|
275
|
+
await retain(join(dir, 'manifest.json'), selectedManifest.raw);
|
|
276
|
+
await retain(join(dir, 'native-before.json'), currentFile.raw);
|
|
277
|
+
await retain(join(dir, 'attempts-before.json'), attemptFile.raw);
|
|
278
|
+
await retain(join(dir, 'server-projection.json'), serverBytes);
|
|
279
|
+
await retain(join(dir, 'complete.json'), Buffer.from(serializeRecoveryDocument(receipt)));
|
|
280
|
+
const finalState = await selected(convergeRunStatePath(common, manifest.target), manifest.nativeStateSha256);
|
|
281
|
+
const finalAttempts = await selected(convergeAttemptStatePath(common, manifest.target), manifest.attemptStateSha256);
|
|
282
|
+
const finalLocal = await deriveLocalAuthority(common, manifest.target, manifest.runId, decodeRecoveryDocument(finalState.text), finalState.sha256, finalAttempts, true);
|
|
283
|
+
const finalServer = await readServerProjection(sink, finalLocal, options);
|
|
284
|
+
assertServerBinding(finalLocal, finalServer);
|
|
285
|
+
if (!isDeepStrictEqual(buildManifest(finalLocal, finalServer, manifest.operationId, manifest.createdAt), manifest)) {
|
|
286
|
+
fail('manifest_binding_mismatch');
|
|
287
|
+
}
|
|
288
|
+
await verifyHistoricalDeliveryReconciliations(common, next);
|
|
289
|
+
await writeState(common, next, ownership);
|
|
290
|
+
await selected(convergeRunStatePath(common, manifest.target), sha256(afterBytes));
|
|
291
|
+
return 'applied';
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/** Local fail-closed verification used before claims and report admission. */
|
|
295
|
+
export async function verifyHistoricalDeliveryReconciliations(common, state) {
|
|
296
|
+
validateHistoricalDeliveryReconciliationAudit(state);
|
|
297
|
+
const audit = state.historicalDeliveryReconciliationAudit ?? [];
|
|
298
|
+
if (audit.length === 0)
|
|
299
|
+
return;
|
|
300
|
+
const root = join(common, 'rcl-historical-delivery-reconciliations');
|
|
301
|
+
await inspectRecoveryDirectory(root, true);
|
|
302
|
+
const prefix = [];
|
|
303
|
+
for (const entry of audit) {
|
|
304
|
+
const manifest = historicalDeliveryManifest(entry);
|
|
305
|
+
const dir = directory(common, manifest);
|
|
306
|
+
await inspectRecoveryDirectory(dir, true);
|
|
307
|
+
await selected(join(dir, 'manifest.json'), entry.manifestSha256);
|
|
308
|
+
const beforeFile = await selected(join(dir, 'native-before.json'), manifest.nativeStateSha256);
|
|
309
|
+
const before = decodeRecoveryDocument(beforeFile.text);
|
|
310
|
+
if (!isDeepStrictEqual(before.historicalDeliveryReconciliationAudit ?? [], prefix))
|
|
311
|
+
fail('audit_prefix');
|
|
312
|
+
const attemptFile = await selected(join(dir, 'attempts-before.json'), manifest.attemptStateSha256);
|
|
313
|
+
const serverFile = await selected(join(dir, 'server-projection.json'), manifest.serverProjectionSha256);
|
|
314
|
+
const server = historicalServerProjectionSchema.parse(decodeRecoveryDocument(serverFile.text));
|
|
315
|
+
const local = await deriveLocalAuthority(common, manifest.target, manifest.runId, before, beforeFile.sha256, attemptFile, false);
|
|
316
|
+
assertServerBinding(local, server);
|
|
317
|
+
if (!isDeepStrictEqual(buildManifest(local, server, manifest.operationId, manifest.createdAt), manifest)) {
|
|
318
|
+
fail('retained_source_mismatch');
|
|
319
|
+
}
|
|
320
|
+
const after = Buffer.from(serializeRecoveryDocument(nextState(before, entry, manifest.createdAt)));
|
|
321
|
+
const receipt = receiptFor(manifest, entry.manifestSha256, sha256(after));
|
|
322
|
+
await selected(join(dir, 'complete.json'), sha256(Buffer.from(serializeRecoveryDocument(receipt))));
|
|
323
|
+
prefix.push(entry);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=historical-delivery-reconciliation.js.map
|