@agent-teams/engineering-foundation 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/contract-json-schema-releases/module.d.ts.map +1 -1
- package/dist/capabilities/contract-json-schema-releases/module.js +4 -17
- package/dist/capabilities/contract-json-schema-releases/module.js.map +1 -1
- package/dist/capabilities/contract-protobuf-evolution/module.d.ts.map +1 -1
- package/dist/capabilities/contract-protobuf-evolution/module.js +4 -17
- package/dist/capabilities/contract-protobuf-evolution/module.js.map +1 -1
- package/dist/capabilities/documentation-local-references/module.d.ts.map +1 -1
- package/dist/capabilities/documentation-local-references/module.js +4 -17
- package/dist/capabilities/documentation-local-references/module.js.map +1 -1
- package/dist/capabilities/executable-specifications/module.d.ts.map +1 -1
- package/dist/capabilities/executable-specifications/module.js +4 -17
- package/dist/capabilities/executable-specifications/module.js.map +1 -1
- package/dist/capabilities/governance-architecture-decisions/module.d.ts.map +1 -1
- package/dist/capabilities/governance-architecture-decisions/module.js +4 -17
- package/dist/capabilities/governance-architecture-decisions/module.js.map +1 -1
- package/dist/capabilities/public-api-compatibility/module.d.ts.map +1 -1
- package/dist/capabilities/public-api-compatibility/module.js +4 -19
- package/dist/capabilities/public-api-compatibility/module.js.map +1 -1
- package/dist/capabilities/quality-gate-runner/module.d.ts.map +1 -1
- package/dist/capabilities/quality-gate-runner/module.js +4 -17
- package/dist/capabilities/quality-gate-runner/module.js.map +1 -1
- package/dist/capabilities/repository-agent-workflow/module.d.ts.map +1 -1
- package/dist/capabilities/repository-agent-workflow/module.js +4 -17
- package/dist/capabilities/repository-agent-workflow/module.js.map +1 -1
- package/dist/capabilities/repository-security-baseline/adapters/outbound/filesystem/workflow-evidence-parser.d.ts.map +1 -1
- package/dist/capabilities/repository-security-baseline/adapters/outbound/filesystem/workflow-evidence-parser.js +33 -0
- package/dist/capabilities/repository-security-baseline/adapters/outbound/filesystem/workflow-evidence-parser.js.map +1 -1
- package/dist/capabilities/repository-security-baseline/application/model/repository-security.d.ts +1 -0
- package/dist/capabilities/repository-security-baseline/application/model/repository-security.d.ts.map +1 -1
- package/dist/capabilities/repository-security-baseline/application/model/repository-security.js.map +1 -1
- package/dist/capabilities/repository-security-baseline/application/policies/dependency-review-ordering.js +1 -1
- package/dist/capabilities/repository-security-baseline/application/policies/dependency-review-ordering.js.map +1 -1
- package/dist/capabilities/repository-security-baseline/application/policies/evaluate-repository-security-tools.js +1 -1
- package/dist/capabilities/repository-security-baseline/application/policies/evaluate-repository-security-tools.js.map +1 -1
- package/dist/capabilities/repository-security-baseline/application/policies/evaluate-repository-security.js +1 -1
- package/dist/capabilities/repository-security-baseline/application/policies/evaluate-repository-security.js.map +1 -1
- package/dist/capabilities/repository-security-baseline/module.d.ts.map +1 -1
- package/dist/capabilities/repository-security-baseline/module.js +4 -17
- package/dist/capabilities/repository-security-baseline/module.js.map +1 -1
- package/dist/capabilities/source-dependencies/module.d.ts.map +1 -1
- package/dist/capabilities/source-dependencies/module.js +4 -19
- package/dist/capabilities/source-dependencies/module.js.map +1 -1
- package/dist/capabilities/suppression-governance/module.d.ts.map +1 -1
- package/dist/capabilities/suppression-governance/module.js +4 -19
- package/dist/capabilities/suppression-governance/module.js.map +1 -1
- package/dist/capabilities/workspace-dependency-declarations/module.d.ts.map +1 -1
- package/dist/capabilities/workspace-dependency-declarations/module.js +4 -18
- package/dist/capabilities/workspace-dependency-declarations/module.js.map +1 -1
- package/dist/capability-runtime.d.ts +8 -0
- package/dist/capability-runtime.d.ts.map +1 -1
- package/dist/capability-runtime.js +66 -0
- package/dist/capability-runtime.js.map +1 -1
- package/dist/check-runner.d.ts.map +1 -1
- package/dist/check-runner.js +16 -10
- package/dist/check-runner.js.map +1 -1
- package/dist/document-authoring/adapters/node/node-document-transaction-coordinator.d.ts.map +1 -1
- package/dist/document-authoring/adapters/node/node-document-transaction-coordinator.js +4 -26
- package/dist/document-authoring/adapters/node/node-document-transaction-coordinator.js.map +1 -1
- package/dist/document-authoring/index.d.ts +6 -1
- package/dist/document-authoring/index.d.ts.map +1 -1
- package/dist/document-authoring/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/local-mode/attach-transaction.d.ts.map +1 -1
- package/dist/local-mode/attach-transaction.js +4 -2
- package/dist/local-mode/attach-transaction.js.map +1 -1
- package/dist/local-mode/index.d.ts +5 -0
- package/dist/local-mode/index.d.ts.map +1 -1
- package/dist/local-mode/index.js +5 -0
- package/dist/local-mode/index.js.map +1 -1
- package/dist/local-mode/inspection.d.ts.map +1 -1
- package/dist/local-mode/inspection.js +1 -6
- package/dist/local-mode/inspection.js.map +1 -1
- package/dist/local-mode/service.d.ts.map +1 -1
- package/dist/local-mode/service.js +4 -2
- package/dist/local-mode/service.js.map +1 -1
- package/dist/mutation/index.d.ts +8 -0
- package/dist/mutation/index.d.ts.map +1 -1
- package/dist/mutation/index.js.map +1 -1
- package/dist/mutation/qualification/index.d.ts +10 -0
- package/dist/mutation/qualification/index.d.ts.map +1 -0
- package/dist/mutation/qualification/index.js +6 -0
- package/dist/mutation/qualification/index.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-effects.d.ts +84 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-effects.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-effects.js +232 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-effects.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-faults.d.ts +9 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-faults.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-faults.js +2 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-faults.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-observation.d.ts +6 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-observation.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-observation.js +30 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-observation.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-operation.d.ts +11 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-operation.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-operation.js +159 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-operation.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-state.d.ts +9 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-state.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-state.js +21 -0
- package/dist/repository-mutation/adapters/node/node-known-file-apply-state.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-cleanup.d.ts +9 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-cleanup.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-cleanup.js +203 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-cleanup.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-executor.d.ts +17 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-executor.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-executor.js +22 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-executor.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-observation.d.ts +15 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-observation.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-observation.js +47 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-observation.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured-cleanup.d.ts +29 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured-cleanup.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured-cleanup.js +72 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured-cleanup.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured.d.ts +31 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured.js +254 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage-captured.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage.d.ts +13 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage.js +210 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-preimage.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-retirement.d.ts +15 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-retirement.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-retirement.js +172 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-retirement.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-state.d.ts +12 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-state.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-state.js +17 -0
- package/dist/repository-mutation/adapters/node/node-known-file-recovery-state.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-lease-release.d.ts +14 -0
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-lease-release.d.ts.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-lease-release.js +23 -0
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-lease-release.js.map +1 -0
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-recovery.d.ts +2 -5
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-recovery.d.ts.map +1 -1
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-recovery.js +30 -861
- package/dist/repository-mutation/adapters/node/node-known-file-transaction-recovery.js.map +1 -1
- package/dist/repository-mutation/adapters/node/node-known-file-transaction.d.ts +3 -9
- package/dist/repository-mutation/adapters/node/node-known-file-transaction.d.ts.map +1 -1
- package/dist/repository-mutation/adapters/node/node-known-file-transaction.js +44 -452
- package/dist/repository-mutation/adapters/node/node-known-file-transaction.js.map +1 -1
- package/dist/repository-mutation/application/policies/classify-known-file-recovery-transition.d.ts +25 -0
- package/dist/repository-mutation/application/policies/classify-known-file-recovery-transition.d.ts.map +1 -0
- package/dist/repository-mutation/application/policies/classify-known-file-recovery-transition.js +19 -0
- package/dist/repository-mutation/application/policies/classify-known-file-recovery-transition.js.map +1 -0
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.d.ts.map +1 -1
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.js +8 -5
- package/dist/scaffolding/adapters/node/filesystem-authority-workspace.js.map +1 -1
- package/dist/transaction-coordination/application/model/transaction-status.d.ts +10 -0
- package/dist/transaction-coordination/application/model/transaction-status.d.ts.map +1 -1
- package/dist/transaction-coordination/application/release-foundation-transaction-lease.d.ts +6 -0
- package/dist/transaction-coordination/application/release-foundation-transaction-lease.d.ts.map +1 -0
- package/dist/transaction-coordination/application/release-foundation-transaction-lease.js +23 -0
- package/dist/transaction-coordination/application/release-foundation-transaction-lease.js.map +1 -0
- package/dist/unexpected-failure.d.ts +9 -0
- package/dist/unexpected-failure.d.ts.map +1 -0
- package/dist/unexpected-failure.js +105 -0
- package/dist/unexpected-failure.js.map +1 -0
- package/package.json +5 -1
|
@@ -1,835 +1,10 @@
|
|
|
1
|
-
import { link, lstat, mkdir, readdir, rename, rmdir, unlink } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { LOCAL_STATE_DIRECTORY } from "../../../foundation-state-contract.js";
|
|
4
|
-
import { installedFoundationVersion } from "../../../package-version.js";
|
|
5
1
|
import { createNodeFoundationTransactionCoordinator } from "../../../transaction-coordination/adapters/node/node-foundation-transaction-coordinator.js";
|
|
6
|
-
import { installedFoundationBuildIdentity } from "../../../transaction-coordination/adapters/node/installed-foundation-build-identity.js";
|
|
7
2
|
import { pruneFoundationStateDirectory } from "../../../transaction-coordination/adapters/node/node-foundation-state-directory.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { cleanupCommittedKnownFileCaptures, knownFileCapturePaths, observeRecoveryFile, prepareRollbackTemporary } from "./node-known-file-recovery-filesystem.js";
|
|
14
|
-
import { NodeKnownFileTransactionJournalStore } from "./node-known-file-transaction-journal-store.js";
|
|
15
|
-
import { compileKnownFileTransactionReceipt, verifyCommittedKnownFilePostimages } from "./node-known-file-transaction.js";
|
|
16
|
-
import { compileRolledBackReceipt } from "./node-known-file-transaction-receipt.js";
|
|
17
|
-
async function persistRecoveryJournal(store, stored, journal) {
|
|
18
|
-
const envelope = compileKnownFileTransactionEnvelope({
|
|
19
|
-
foundation: stored.envelope.foundation,
|
|
20
|
-
journal,
|
|
21
|
-
state: stored.envelope.state
|
|
22
|
-
});
|
|
23
|
-
stored.authority = await store.replace(stored.authority, envelope);
|
|
24
|
-
stored.envelope = envelope;
|
|
25
|
-
}
|
|
26
|
-
function replaceRecoveryOperation(journal, index, operation) {
|
|
27
|
-
return Object.freeze({
|
|
28
|
-
...journal,
|
|
29
|
-
operations: Object.freeze(journal.operations.with(index, operation))
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
function clearOperationRetirement(operation) {
|
|
33
|
-
if (operation.retirement === undefined) {
|
|
34
|
-
return operation;
|
|
35
|
-
}
|
|
36
|
-
const { retirement: _retirement, ...cleared } = operation;
|
|
37
|
-
return Object.freeze(cleared);
|
|
38
|
-
}
|
|
39
|
-
function retirementPath(options) {
|
|
40
|
-
const leaf = basename(options.operation.path);
|
|
41
|
-
const directory = join(options.parent, `.${leaf}.agent-teams.retire.${options.planDigest.slice(7, 23)}.${options.operationIndex}.${options.kind}`);
|
|
42
|
-
const source = options.kind === "destination"
|
|
43
|
-
? join(options.parent, leaf)
|
|
44
|
-
: options.kind === "temporary"
|
|
45
|
-
? join(options.parent, knownFileTemporaryName(options.operation.path, options.planDigest, options.operationIndex))
|
|
46
|
-
: join(options.parent, `.${leaf}.agent-teams.rollback.${options.operationIndex}.tmp`);
|
|
47
|
-
return { captured: join(directory, "captured"), directory, source };
|
|
48
|
-
}
|
|
49
|
-
// oxlint-disable-next-line complexity, max-lines-per-function
|
|
50
|
-
async function retireJournalBoundPath(options) {
|
|
51
|
-
const operation = options.stored.envelope.journal.plan.operations[options.operationIndex];
|
|
52
|
-
const parent = dirname(join(options.root, ...operation.path.split("/")));
|
|
53
|
-
const paths = retirementPath({
|
|
54
|
-
kind: options.kind,
|
|
55
|
-
operation,
|
|
56
|
-
operationIndex: options.operationIndex,
|
|
57
|
-
parent,
|
|
58
|
-
planDigest: options.stored.envelope.journal.plan.planDigest
|
|
59
|
-
});
|
|
60
|
-
const initialJournalOperation = options.stored.envelope.journal.operations[options.operationIndex];
|
|
61
|
-
if (!("temporaryIdentity" in initialJournalOperation)) {
|
|
62
|
-
throw new KnownFileTransactionError("KNOWN_FILE_JOURNAL_INVALID", `Retirement requires durable operation identity: ${operation.path}.`);
|
|
63
|
-
}
|
|
64
|
-
let journalOperation = initialJournalOperation;
|
|
65
|
-
let retirement = journalOperation.retirement;
|
|
66
|
-
if (retirement === undefined) {
|
|
67
|
-
if (await pathMatchesRegularFileIdentity(paths.source, options.expectedIdentity) !== "match") {
|
|
68
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Identity-bound recovery path changed before retirement: ${operation.path}.`);
|
|
69
|
-
}
|
|
70
|
-
const directory = await mkdir(paths.directory, { mode: 0o700 }).then(() => lstat(paths.directory, { bigint: true })).catch((error) => {
|
|
71
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Retirement directory could not be exclusively created: ${operation.path}.`, { cause: error });
|
|
72
|
-
});
|
|
73
|
-
await syncDirectoryStrictly(parent);
|
|
74
|
-
const durableDirectory = await lstat(paths.directory, { bigint: true }).catch((error) => {
|
|
75
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Retirement directory disappeared before durable identity binding: ${operation.path}.`, { cause: error });
|
|
76
|
-
});
|
|
77
|
-
if (!durableDirectory.isDirectory() || durableDirectory.isSymbolicLink() ||
|
|
78
|
-
durableDirectory.birthtimeNs !== directory.birthtimeNs ||
|
|
79
|
-
durableDirectory.dev !== directory.dev || durableDirectory.ino !== directory.ino) {
|
|
80
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Retirement directory changed before durable identity binding: ${operation.path}.`);
|
|
81
|
-
}
|
|
82
|
-
retirement = Object.freeze({
|
|
83
|
-
kind: options.kind,
|
|
84
|
-
state: "ready",
|
|
85
|
-
directoryIdentity: serializeKnownFileIdentity(directory),
|
|
86
|
-
pathIdentity: serializeKnownFileIdentity(options.expectedIdentity)
|
|
87
|
-
});
|
|
88
|
-
journalOperation = Object.freeze({ ...journalOperation, retirement });
|
|
89
|
-
await persistRecoveryJournal(options.store, options.stored, replaceRecoveryOperation(options.stored.envelope.journal, options.operationIndex, journalOperation));
|
|
90
|
-
await options.faultInjector?.({
|
|
91
|
-
phase: "after-retirement-directory-bound",
|
|
92
|
-
operationIndex: options.operationIndex,
|
|
93
|
-
path: operation.path
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
if (retirement.kind !== options.kind ||
|
|
97
|
-
!sameKnownFileIdentity(deserializeKnownFileIdentity(retirement.pathIdentity), options.expectedIdentity)) {
|
|
98
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Retirement authority does not match the requested path: ${operation.path}.`);
|
|
99
|
-
}
|
|
100
|
-
const expectedDirectory = deserializeKnownFileIdentity(retirement.directoryIdentity);
|
|
101
|
-
const directory = await lstat(paths.directory, { bigint: true }).catch((error) => {
|
|
102
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
throw error;
|
|
106
|
-
});
|
|
107
|
-
if (directory === null) {
|
|
108
|
-
if (retirement.state !== "unlink-authorized" ||
|
|
109
|
-
await pathMatchesRegularFileIdentity(paths.source, options.expectedIdentity) === "match") {
|
|
110
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Journal-bound retirement directory disappeared: ${operation.path}.`);
|
|
111
|
-
}
|
|
112
|
-
const cleared = clearOperationRetirement(journalOperation);
|
|
113
|
-
await persistRecoveryJournal(options.store, options.stored, replaceRecoveryOperation(options.stored.envelope.journal, options.operationIndex, cleared));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (!directory.isDirectory() || directory.isSymbolicLink() ||
|
|
117
|
-
directory.birthtimeNs !== expectedDirectory.birthtimeNs ||
|
|
118
|
-
directory.dev !== expectedDirectory.dev || directory.ino !== expectedDirectory.ino) {
|
|
119
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Journal-bound retirement directory changed: ${operation.path}.`);
|
|
120
|
-
}
|
|
121
|
-
const capturedMatch = await pathMatchesRegularFileIdentity(paths.captured, options.expectedIdentity);
|
|
122
|
-
const sourceMatch = await pathMatchesRegularFileIdentity(paths.source, options.expectedIdentity);
|
|
123
|
-
if (retirement.state === "ready") {
|
|
124
|
-
if (capturedMatch === "missing" && sourceMatch === "match") {
|
|
125
|
-
await rename(paths.source, paths.captured);
|
|
126
|
-
await syncDirectoryStrictly(paths.directory);
|
|
127
|
-
await syncDirectoryStrictly(parent);
|
|
128
|
-
}
|
|
129
|
-
else if (capturedMatch !== "match" || sourceMatch === "match") {
|
|
130
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Journal-bound retirement capture is ambiguous: ${operation.path}.`);
|
|
131
|
-
}
|
|
132
|
-
retirement = Object.freeze({ ...retirement, state: "captured" });
|
|
133
|
-
journalOperation = Object.freeze({ ...journalOperation, retirement });
|
|
134
|
-
await persistRecoveryJournal(options.store, options.stored, replaceRecoveryOperation(options.stored.envelope.journal, options.operationIndex, journalOperation));
|
|
135
|
-
await options.faultInjector?.({
|
|
136
|
-
phase: "after-retirement-captured",
|
|
137
|
-
operationIndex: options.operationIndex,
|
|
138
|
-
path: operation.path
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
if (retirement.state === "captured") {
|
|
142
|
-
if (await pathMatchesRegularFileIdentity(paths.captured, options.expectedIdentity) !== "match") {
|
|
143
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Journal-bound retired bytes changed: ${operation.path}.`);
|
|
144
|
-
}
|
|
145
|
-
retirement = Object.freeze({ ...retirement, state: "unlink-authorized" });
|
|
146
|
-
journalOperation = Object.freeze({ ...journalOperation, retirement });
|
|
147
|
-
await persistRecoveryJournal(options.store, options.stored, replaceRecoveryOperation(options.stored.envelope.journal, options.operationIndex, journalOperation));
|
|
148
|
-
await options.faultInjector?.({
|
|
149
|
-
phase: "after-retirement-unlink-authorized",
|
|
150
|
-
operationIndex: options.operationIndex,
|
|
151
|
-
path: operation.path
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
const capturedAfterAuthorization = await pathMatchesRegularFileIdentity(paths.captured, options.expectedIdentity);
|
|
155
|
-
if (capturedAfterAuthorization === "match") {
|
|
156
|
-
await unlink(paths.captured);
|
|
157
|
-
await syncDirectoryStrictly(paths.directory);
|
|
158
|
-
}
|
|
159
|
-
else if (capturedAfterAuthorization !== "missing") {
|
|
160
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Authorized retirement capture became foreign: ${operation.path}.`);
|
|
161
|
-
}
|
|
162
|
-
if ((await readdir(paths.directory)).length !== 0) {
|
|
163
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Retirement directory contains foreign evidence: ${operation.path}.`);
|
|
164
|
-
}
|
|
165
|
-
await rmdir(paths.directory);
|
|
166
|
-
await syncDirectoryStrictly(parent);
|
|
167
|
-
const cleared = clearOperationRetirement(journalOperation);
|
|
168
|
-
await persistRecoveryJournal(options.store, options.stored, replaceRecoveryOperation(options.stored.envelope.journal, options.operationIndex, cleared));
|
|
169
|
-
}
|
|
170
|
-
async function restoreAbsentPreimage(options) {
|
|
171
|
-
if (options.observed.state === "absent") {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (!matchesKnownFileImage(options.observed, options.operation.postimage) ||
|
|
175
|
-
options.observed.identity === undefined ||
|
|
176
|
-
!("temporaryIdentity" in options.journalOperation) ||
|
|
177
|
-
!sameKnownFileIdentity(options.observed.identity, deserializeKnownFileIdentity(options.journalOperation.temporaryIdentity))) {
|
|
178
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Created destination changed after publication: ${options.operation.path}.`);
|
|
179
|
-
}
|
|
180
|
-
await retireJournalBoundPath({
|
|
181
|
-
expectedIdentity: options.observed.identity,
|
|
182
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
183
|
-
kind: "destination",
|
|
184
|
-
operationIndex: options.operationIndex,
|
|
185
|
-
root: options.root,
|
|
186
|
-
store: options.store,
|
|
187
|
-
stored: options.stored
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
async function acceptRestoredPreimage(options) {
|
|
191
|
-
if (!matchesKnownFileImage(options.observed, options.preimage)) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
if (options.journalOperation.rollbackTemporaryIdentity !== undefined &&
|
|
195
|
-
options.observed.identity !== undefined &&
|
|
196
|
-
sameKnownFileIdentity(options.observed.identity, deserializeKnownFileIdentity(options.journalOperation.rollbackTemporaryIdentity))) {
|
|
197
|
-
await retireJournalBoundPath({
|
|
198
|
-
expectedIdentity: options.observed.identity,
|
|
199
|
-
kind: "rollback-temporary",
|
|
200
|
-
operationIndex: options.operationIndex,
|
|
201
|
-
root: options.root,
|
|
202
|
-
store: options.store,
|
|
203
|
-
stored: options.stored
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
function assertPublishedIdentity(options) {
|
|
209
|
-
if (options.observed.identity === undefined ||
|
|
210
|
-
!sameKnownFileIdentity(options.observed.identity, deserializeKnownFileIdentity(options.journalOperation.temporaryIdentity))) {
|
|
211
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Published destination identity changed before rollback: ${options.operationPath}.`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
// oxlint-disable-next-line complexity, max-lines-per-function
|
|
215
|
-
async function restorePreimage(options) {
|
|
216
|
-
const operation = options.operation;
|
|
217
|
-
let journalOperation = options.stored.envelope.journal.operations[options.operationIndex];
|
|
218
|
-
const destination = join(options.root, ...operation.path.split("/"));
|
|
219
|
-
const parent = dirname(destination);
|
|
220
|
-
if (operation.precondition.state === "absent") {
|
|
221
|
-
const observed = await observeRecoveryFile(destination, maximumKnownFileEvidenceBytes(operation));
|
|
222
|
-
await restoreAbsentPreimage({
|
|
223
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
224
|
-
journalOperation,
|
|
225
|
-
observed,
|
|
226
|
-
operation,
|
|
227
|
-
operationIndex: options.operationIndex,
|
|
228
|
-
root: options.root,
|
|
229
|
-
store: options.store,
|
|
230
|
-
stored: options.stored
|
|
231
|
-
});
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (journalOperation.captureDirectoryIdentity !== undefined) {
|
|
235
|
-
await restoreCapturedPreimage({
|
|
236
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
237
|
-
journalOperation: journalOperation,
|
|
238
|
-
operation: { ...operation, precondition: operation.precondition },
|
|
239
|
-
operationIndex: options.operationIndex,
|
|
240
|
-
planDigest: options.stored.envelope.journal.plan.planDigest,
|
|
241
|
-
root: options.root,
|
|
242
|
-
store: options.store,
|
|
243
|
-
stored: options.stored
|
|
244
|
-
});
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const observed = await observeRecoveryFile(destination, maximumKnownFileEvidenceBytes(operation));
|
|
248
|
-
const postMatches = matchesKnownFileImage(observed, operation.postimage);
|
|
249
|
-
if (!("temporaryIdentity" in journalOperation)) {
|
|
250
|
-
throw new KnownFileTransactionError("KNOWN_FILE_JOURNAL_INVALID", `Published operation lacks temporary identity authority: ${operation.path}.`);
|
|
251
|
-
}
|
|
252
|
-
const matched = journalOperation.matchedPreimage;
|
|
253
|
-
if (matched === undefined) {
|
|
254
|
-
throw new KnownFileTransactionError("KNOWN_FILE_JOURNAL_INVALID", `Replacement preimage binding is absent: ${operation.path}.`);
|
|
255
|
-
}
|
|
256
|
-
const preimage = operation.precondition.acceptedPreimages[matched];
|
|
257
|
-
if (await acceptRestoredPreimage({
|
|
258
|
-
journalOperation,
|
|
259
|
-
observed,
|
|
260
|
-
operationIndex: options.operationIndex,
|
|
261
|
-
operationPath: operation.path,
|
|
262
|
-
preimage,
|
|
263
|
-
root: options.root,
|
|
264
|
-
store: options.store,
|
|
265
|
-
stored: options.stored
|
|
266
|
-
})) {
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
const resumingAfterRetirement = observed.state === "absent" &&
|
|
270
|
-
journalOperation.rollbackTemporaryIdentity !== undefined;
|
|
271
|
-
if (!postMatches && !resumingAfterRetirement) {
|
|
272
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Published destination changed before rollback: ${operation.path}.`);
|
|
273
|
-
}
|
|
274
|
-
if (postMatches) {
|
|
275
|
-
assertPublishedIdentity({ journalOperation, observed, operationPath: operation.path });
|
|
276
|
-
}
|
|
277
|
-
const rollbackPath = join(parent, `.${basename(operation.path)}.agent-teams.rollback.${options.operationIndex}.tmp`);
|
|
278
|
-
const rollbackIdentity = await prepareRollbackTemporary({
|
|
279
|
-
...(journalOperation.rollbackTemporaryIdentity === undefined
|
|
280
|
-
? {}
|
|
281
|
-
: {
|
|
282
|
-
expectedIdentity: deserializeKnownFileIdentity(journalOperation.rollbackTemporaryIdentity)
|
|
283
|
-
}),
|
|
284
|
-
operationPath: operation.path,
|
|
285
|
-
path: rollbackPath,
|
|
286
|
-
preimage
|
|
287
|
-
});
|
|
288
|
-
await syncDirectoryStrictly(parent);
|
|
289
|
-
if (await pathMatchesRegularFileIdentity(rollbackPath, rollbackIdentity) !== "match") {
|
|
290
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback temporary changed before durable identity binding: ${operation.path}.`);
|
|
291
|
-
}
|
|
292
|
-
if (journalOperation.rollbackTemporaryIdentity === undefined) {
|
|
293
|
-
journalOperation = Object.freeze({
|
|
294
|
-
...journalOperation,
|
|
295
|
-
rollbackTemporaryIdentity: serializeKnownFileIdentity(rollbackIdentity)
|
|
296
|
-
});
|
|
297
|
-
const journal = Object.freeze({
|
|
298
|
-
...options.stored.envelope.journal,
|
|
299
|
-
operations: Object.freeze(options.stored.envelope.journal.operations.with(options.operationIndex, journalOperation))
|
|
300
|
-
});
|
|
301
|
-
await persistRecoveryJournal(options.store, options.stored, journal);
|
|
302
|
-
}
|
|
303
|
-
if (!resumingAfterRetirement) {
|
|
304
|
-
await retireJournalBoundPath({
|
|
305
|
-
expectedIdentity: observed.identity,
|
|
306
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
307
|
-
kind: "destination",
|
|
308
|
-
operationIndex: options.operationIndex,
|
|
309
|
-
root: options.root,
|
|
310
|
-
store: options.store,
|
|
311
|
-
stored: options.stored
|
|
312
|
-
});
|
|
313
|
-
await options.faultInjector?.({
|
|
314
|
-
phase: "after-destination-retired",
|
|
315
|
-
operationIndex: options.operationIndex,
|
|
316
|
-
path: operation.path
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
const prepared = await readBoundedRegularFile(rollbackPath, preimage.size);
|
|
320
|
-
if (prepared.outcome !== "read" ||
|
|
321
|
-
!sameKnownFileIdentity(prepared.identity, rollbackIdentity) ||
|
|
322
|
-
!matchesKnownFileImage({
|
|
323
|
-
state: "file", bytes: prepared.bytes, identity: prepared.identity,
|
|
324
|
-
mode: prepared.mode & 0o777
|
|
325
|
-
}, preimage)) {
|
|
326
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback temporary changed before copy: ${operation.path}.`);
|
|
327
|
-
}
|
|
328
|
-
await link(rollbackPath, destination).catch((error) => {
|
|
329
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Destination appeared during rollback CAS: ${operation.path}.`, { cause: error });
|
|
330
|
-
});
|
|
331
|
-
await options.faultInjector?.({
|
|
332
|
-
phase: "after-rollback-linked",
|
|
333
|
-
operationIndex: options.operationIndex,
|
|
334
|
-
path: operation.path
|
|
335
|
-
});
|
|
336
|
-
await syncDirectoryStrictly(parent);
|
|
337
|
-
const restored = await observeRecoveryFile(destination, preimage.size);
|
|
338
|
-
if (!matchesKnownFileImage(restored, preimage) || restored.identity === undefined ||
|
|
339
|
-
!sameKnownFileIdentity(restored.identity, rollbackIdentity)) {
|
|
340
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_FAILED", `Preimage restoration failed: ${operation.path}.`);
|
|
341
|
-
}
|
|
342
|
-
await retireJournalBoundPath({
|
|
343
|
-
expectedIdentity: rollbackIdentity,
|
|
344
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
345
|
-
kind: "rollback-temporary",
|
|
346
|
-
operationIndex: options.operationIndex,
|
|
347
|
-
root: options.root,
|
|
348
|
-
store: options.store,
|
|
349
|
-
stored: options.stored
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
// oxlint-disable-next-line complexity, max-lines-per-function
|
|
353
|
-
async function restoreCapturedPreimage(options) {
|
|
354
|
-
const destination = join(options.root, ...options.operation.path.split("/"));
|
|
355
|
-
const parent = dirname(destination);
|
|
356
|
-
const paths = knownFileCapturePaths({
|
|
357
|
-
operationIndex: options.operationIndex,
|
|
358
|
-
operationPath: options.operation.path,
|
|
359
|
-
parent,
|
|
360
|
-
planDigest: options.planDigest
|
|
361
|
-
});
|
|
362
|
-
const recoveryMaximumBytes = Math.max(maximumKnownFileEvidenceBytes(options.operation), 8 * 1024 * 1024);
|
|
363
|
-
if (options.journalOperation.state === "rollback-restored") {
|
|
364
|
-
const matched = options.journalOperation.matchedPreimage;
|
|
365
|
-
if (matched === undefined) {
|
|
366
|
-
throw new KnownFileTransactionError("KNOWN_FILE_JOURNAL_INVALID", `Restored replacement lacks exact preimage authority: ${options.operation.path}.`);
|
|
367
|
-
}
|
|
368
|
-
const preimage = options.operation.precondition.acceptedPreimages[matched];
|
|
369
|
-
const destinationObserved = await observeRecoveryFile(destination, recoveryMaximumBytes);
|
|
370
|
-
if (!matchesKnownFileImage(destinationObserved, preimage)) {
|
|
371
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Restored destination changed during cleanup: ${options.operation.path}.`);
|
|
372
|
-
}
|
|
373
|
-
const rollbackPath = join(parent, `.${basename(options.operation.path)}.agent-teams.rollback.${options.operationIndex}.tmp`);
|
|
374
|
-
if (options.journalOperation.rollbackTemporaryIdentity !== undefined) {
|
|
375
|
-
const rollbackObserved = await observeRecoveryFile(rollbackPath, preimage.size);
|
|
376
|
-
if (rollbackObserved.state === "file") {
|
|
377
|
-
const expectedRollback = deserializeKnownFileIdentity(options.journalOperation.rollbackTemporaryIdentity);
|
|
378
|
-
if (rollbackObserved.identity === undefined ||
|
|
379
|
-
!sameKnownFileIdentity(rollbackObserved.identity, expectedRollback) ||
|
|
380
|
-
!matchesKnownFileImage(rollbackObserved, preimage)) {
|
|
381
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback copy changed during cleanup: ${options.operation.path}.`);
|
|
382
|
-
}
|
|
383
|
-
await unlink(rollbackPath);
|
|
384
|
-
await syncDirectoryStrictly(parent);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const directory = await lstat(paths.directory, { bigint: true }).catch((error) => {
|
|
388
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
throw error;
|
|
392
|
-
});
|
|
393
|
-
if (directory === null) {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
const expectedDirectory = deserializeKnownFileIdentity(options.journalOperation.captureDirectoryIdentity);
|
|
397
|
-
if (!directory.isDirectory() || directory.isSymbolicLink() ||
|
|
398
|
-
directory.birthtimeNs !== expectedDirectory.birthtimeNs ||
|
|
399
|
-
directory.dev !== expectedDirectory.dev || directory.ino !== expectedDirectory.ino) {
|
|
400
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback capture directory changed during cleanup: ${options.operation.path}.`);
|
|
401
|
-
}
|
|
402
|
-
const capturedObserved = await observeRecoveryFile(paths.captured, recoveryMaximumBytes);
|
|
403
|
-
if (capturedObserved.state === "file") {
|
|
404
|
-
const expectedCaptured = options.journalOperation.capturedPreimageIdentity;
|
|
405
|
-
if (expectedCaptured === undefined || capturedObserved.identity === undefined ||
|
|
406
|
-
!sameKnownFileIdentity(capturedObserved.identity, deserializeKnownFileIdentity(expectedCaptured)) || !matchesKnownFileImage(capturedObserved, preimage)) {
|
|
407
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback capture changed during cleanup: ${options.operation.path}.`);
|
|
408
|
-
}
|
|
409
|
-
await unlink(paths.captured);
|
|
410
|
-
await syncDirectoryStrictly(paths.directory);
|
|
411
|
-
await options.faultInjector?.({
|
|
412
|
-
phase: "after-rollback-capture-unlinked",
|
|
413
|
-
operationIndex: options.operationIndex,
|
|
414
|
-
path: options.operation.path
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
const retiredObserved = await observeRecoveryFile(paths.retired, recoveryMaximumBytes);
|
|
418
|
-
if (retiredObserved.state !== "absent") {
|
|
419
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback capture retains foreign retired evidence: ${options.operation.path}.`);
|
|
420
|
-
}
|
|
421
|
-
await rmdir(paths.directory);
|
|
422
|
-
await syncDirectoryStrictly(parent);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
const expectedDirectory = deserializeKnownFileIdentity(options.journalOperation.captureDirectoryIdentity);
|
|
426
|
-
const directory = await lstat(paths.directory, { bigint: true }).catch((error) => {
|
|
427
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
428
|
-
return null;
|
|
429
|
-
}
|
|
430
|
-
throw error;
|
|
431
|
-
});
|
|
432
|
-
if (directory === null || !directory.isDirectory() || directory.isSymbolicLink() ||
|
|
433
|
-
directory.birthtimeNs !== expectedDirectory.birthtimeNs ||
|
|
434
|
-
directory.dev !== expectedDirectory.dev || directory.ino !== expectedDirectory.ino) {
|
|
435
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Replacement capture directory changed: ${options.operation.path}.`);
|
|
436
|
-
}
|
|
437
|
-
const captured = await observeRecoveryFile(paths.captured, recoveryMaximumBytes);
|
|
438
|
-
const destinationBefore = await observeRecoveryFile(destination, recoveryMaximumBytes);
|
|
439
|
-
const retired = await observeRecoveryFile(paths.retired, recoveryMaximumBytes);
|
|
440
|
-
const matched = options.journalOperation.matchedPreimage;
|
|
441
|
-
if (matched === undefined) {
|
|
442
|
-
throw new KnownFileTransactionError("KNOWN_FILE_JOURNAL_INVALID", `Captured replacement lacks its exact preimage binding: ${options.operation.path}.`);
|
|
443
|
-
}
|
|
444
|
-
const preimage = options.operation.precondition.acceptedPreimages[matched];
|
|
445
|
-
if (options.journalOperation.capturedPreimageIdentity === undefined) {
|
|
446
|
-
if (retired.state !== "absent" || captured.state !== "absent") {
|
|
447
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Replacement capture contains bytes without durable identity authority: ${options.operation.path}.`);
|
|
448
|
-
}
|
|
449
|
-
const stableDestination = await readBoundedRegularFile(destination, preimage.size);
|
|
450
|
-
if (stableDestination.outcome !== "read" || stableDestination.linkCount !== 1n ||
|
|
451
|
-
!matchesKnownFileImage({
|
|
452
|
-
state: "file",
|
|
453
|
-
bytes: stableDestination.bytes,
|
|
454
|
-
identity: stableDestination.identity,
|
|
455
|
-
mode: stableDestination.mode & 0o777
|
|
456
|
-
}, preimage)) {
|
|
457
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Uncaptured replacement preimage is not an exclusive exact file: ${options.operation.path}.`);
|
|
458
|
-
}
|
|
459
|
-
const restoredOperation = Object.freeze({
|
|
460
|
-
...options.journalOperation,
|
|
461
|
-
state: "rollback-restored"
|
|
462
|
-
});
|
|
463
|
-
await persistRecoveryJournal(options.store, options.stored, replaceRecoveryOperation(options.stored.envelope.journal, options.operationIndex, restoredOperation));
|
|
464
|
-
await rmdir(paths.directory);
|
|
465
|
-
await syncDirectoryStrictly(parent);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
const capturedIdentity = deserializeKnownFileIdentity(options.journalOperation.capturedPreimageIdentity);
|
|
469
|
-
const captureIdentityMatches = captured.state === "file" && captured.identity !== undefined &&
|
|
470
|
-
sameKnownFileIdentity(captured.identity, capturedIdentity);
|
|
471
|
-
if (!captureIdentityMatches) {
|
|
472
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Captured replacement identity changed: ${options.operation.path}.`);
|
|
473
|
-
}
|
|
474
|
-
const captureBytesStable = matchesKnownFileImage(captured, preimage);
|
|
475
|
-
const rollbackPath = join(parent, `.${basename(options.operation.path)}.agent-teams.rollback.${options.operationIndex}.tmp`);
|
|
476
|
-
const rollbackIdentity = options.journalOperation.rollbackTemporaryIdentity === undefined
|
|
477
|
-
? undefined
|
|
478
|
-
: deserializeKnownFileIdentity(options.journalOperation.rollbackTemporaryIdentity);
|
|
479
|
-
const rollbackObserved = rollbackIdentity === undefined
|
|
480
|
-
? { state: "absent" }
|
|
481
|
-
: await observeRecoveryFile(rollbackPath, preimage.size);
|
|
482
|
-
if (rollbackIdentity !== undefined &&
|
|
483
|
-
(rollbackObserved.state !== "file" || rollbackObserved.identity === undefined ||
|
|
484
|
-
!sameKnownFileIdentity(rollbackObserved.identity, rollbackIdentity) ||
|
|
485
|
-
!matchesKnownFileImage(rollbackObserved, preimage))) {
|
|
486
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Rollback preimage copy changed: ${options.operation.path}.`);
|
|
487
|
-
}
|
|
488
|
-
if (rollbackIdentity === undefined && !captureBytesStable) {
|
|
489
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Captured preimage changed before a stable rollback copy existed: ${options.operation.path}.`);
|
|
490
|
-
}
|
|
491
|
-
const rollbackSource = rollbackIdentity === undefined ? paths.captured : rollbackPath;
|
|
492
|
-
const rollbackSourceIdentity = rollbackIdentity ?? capturedIdentity;
|
|
493
|
-
if (retired.state === "file") {
|
|
494
|
-
if (retired.identity !== undefined &&
|
|
495
|
-
sameKnownFileIdentity(retired.identity, capturedIdentity)) {
|
|
496
|
-
await unlink(paths.retired);
|
|
497
|
-
await syncDirectoryStrictly(paths.directory);
|
|
498
|
-
}
|
|
499
|
-
else {
|
|
500
|
-
if (destinationBefore.state === "absent") {
|
|
501
|
-
await link(paths.retired, destination);
|
|
502
|
-
await syncDirectoryStrictly(parent);
|
|
503
|
-
await unlink(paths.retired);
|
|
504
|
-
await syncDirectoryStrictly(paths.directory);
|
|
505
|
-
}
|
|
506
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Foreign destination was preserved from an interrupted capture: ${options.operation.path}.`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
let observed = await observeRecoveryFile(destination, recoveryMaximumBytes);
|
|
510
|
-
if (matchesKnownFileImage(observed, preimage)) {
|
|
511
|
-
// A foreign exact preimage is already the correct rollback result. Never
|
|
512
|
-
// replace it merely to recover the original inode.
|
|
513
|
-
if (observed.identity === undefined ||
|
|
514
|
-
!sameKnownFileIdentity(observed.identity, rollbackSourceIdentity)) {
|
|
515
|
-
const restoredJournalOperation = Object.freeze({
|
|
516
|
-
...options.journalOperation,
|
|
517
|
-
state: "rollback-restored"
|
|
518
|
-
});
|
|
519
|
-
await persistRecoveryJournal(options.store, options.stored, Object.freeze({
|
|
520
|
-
...options.stored.envelope.journal,
|
|
521
|
-
operations: Object.freeze(options.stored.envelope.journal.operations.with(options.operationIndex, restoredJournalOperation))
|
|
522
|
-
}));
|
|
523
|
-
if (rollbackIdentity !== undefined) {
|
|
524
|
-
await unlink(rollbackPath);
|
|
525
|
-
}
|
|
526
|
-
if (captureBytesStable) {
|
|
527
|
-
await unlink(paths.captured);
|
|
528
|
-
}
|
|
529
|
-
await syncDirectoryStrictly(paths.directory);
|
|
530
|
-
if (captureBytesStable) {
|
|
531
|
-
await options.faultInjector?.({
|
|
532
|
-
phase: "after-rollback-capture-unlinked",
|
|
533
|
-
operationIndex: options.operationIndex,
|
|
534
|
-
path: options.operation.path
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
if (captureBytesStable) {
|
|
538
|
-
await rmdir(paths.directory);
|
|
539
|
-
}
|
|
540
|
-
await syncDirectoryStrictly(parent);
|
|
541
|
-
if (!captureBytesStable) {
|
|
542
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Edited retired inode was preserved after exact rollback: ${options.operation.path}.`);
|
|
543
|
-
}
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
else if (observed.state === "file") {
|
|
548
|
-
const temporaryIdentity = deserializeKnownFileIdentity(options.journalOperation.temporaryIdentity);
|
|
549
|
-
if (!matchesKnownFileImage(observed, options.operation.postimage) ||
|
|
550
|
-
observed.identity === undefined ||
|
|
551
|
-
!sameKnownFileIdentity(observed.identity, temporaryIdentity)) {
|
|
552
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Destination changed before captured-preimage rollback: ${options.operation.path}.`);
|
|
553
|
-
}
|
|
554
|
-
await retireJournalBoundPath({
|
|
555
|
-
expectedIdentity: temporaryIdentity,
|
|
556
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
557
|
-
kind: "destination",
|
|
558
|
-
operationIndex: options.operationIndex,
|
|
559
|
-
root: options.root,
|
|
560
|
-
store: options.store,
|
|
561
|
-
stored: options.stored
|
|
562
|
-
});
|
|
563
|
-
await options.faultInjector?.({
|
|
564
|
-
phase: "after-destination-retired",
|
|
565
|
-
operationIndex: options.operationIndex,
|
|
566
|
-
path: options.operation.path
|
|
567
|
-
});
|
|
568
|
-
observed = { state: "absent" };
|
|
569
|
-
}
|
|
570
|
-
if (observed.state === "absent") {
|
|
571
|
-
await link(rollbackSource, destination).catch((error) => {
|
|
572
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Destination appeared during captured-preimage rollback: ${options.operation.path}.`, { cause: error });
|
|
573
|
-
});
|
|
574
|
-
await options.faultInjector?.({
|
|
575
|
-
phase: "after-rollback-linked",
|
|
576
|
-
operationIndex: options.operationIndex,
|
|
577
|
-
path: options.operation.path
|
|
578
|
-
});
|
|
579
|
-
await syncDirectoryStrictly(parent);
|
|
580
|
-
}
|
|
581
|
-
const restored = await observeRecoveryFile(destination, preimage.size);
|
|
582
|
-
if (!matchesKnownFileImage(restored, preimage) || restored.identity === undefined ||
|
|
583
|
-
!sameKnownFileIdentity(restored.identity, rollbackSourceIdentity)) {
|
|
584
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_FAILED", `Captured preimage restoration failed: ${options.operation.path}.`);
|
|
585
|
-
}
|
|
586
|
-
const restoredJournalOperation = Object.freeze({
|
|
587
|
-
...options.journalOperation,
|
|
588
|
-
state: "rollback-restored"
|
|
589
|
-
});
|
|
590
|
-
const restoredJournal = Object.freeze({
|
|
591
|
-
...options.stored.envelope.journal,
|
|
592
|
-
operations: Object.freeze(options.stored.envelope.journal.operations.with(options.operationIndex, restoredJournalOperation))
|
|
593
|
-
});
|
|
594
|
-
await persistRecoveryJournal(options.store, options.stored, restoredJournal);
|
|
595
|
-
if (rollbackIdentity !== undefined) {
|
|
596
|
-
await unlink(rollbackPath);
|
|
597
|
-
}
|
|
598
|
-
if (captureBytesStable) {
|
|
599
|
-
await unlink(paths.captured);
|
|
600
|
-
}
|
|
601
|
-
await syncDirectoryStrictly(paths.directory);
|
|
602
|
-
if (captureBytesStable) {
|
|
603
|
-
await options.faultInjector?.({
|
|
604
|
-
phase: "after-rollback-capture-unlinked",
|
|
605
|
-
operationIndex: options.operationIndex,
|
|
606
|
-
path: options.operation.path
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
if (captureBytesStable) {
|
|
610
|
-
await rmdir(paths.directory);
|
|
611
|
-
}
|
|
612
|
-
await syncDirectoryStrictly(parent);
|
|
613
|
-
if (!captureBytesStable) {
|
|
614
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Edited retired inode was preserved after exact rollback: ${options.operation.path}.`);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
async function cleanupOperationTemporaries(options) {
|
|
618
|
-
const operation = options.stored.envelope.journal.plan.operations[options.operationIndex];
|
|
619
|
-
const journalOperation = options.stored.envelope.journal.operations[options.operationIndex];
|
|
620
|
-
const parent = dirname(join(options.root, ...operation.path.split("/")));
|
|
621
|
-
const candidates = [
|
|
622
|
-
{
|
|
623
|
-
identity: "temporaryIdentity" in journalOperation
|
|
624
|
-
? deserializeKnownFileIdentity(journalOperation.temporaryIdentity)
|
|
625
|
-
: undefined,
|
|
626
|
-
image: operation.postimage,
|
|
627
|
-
kind: "temporary",
|
|
628
|
-
authorizedWithoutIdentity: journalOperation.state === "temporary-authorized",
|
|
629
|
-
path: join(parent, knownFileTemporaryName(operation.path, options.stored.envelope.journal.plan.planDigest, options.operationIndex))
|
|
630
|
-
},
|
|
631
|
-
...(operation.precondition.state === "known-file" &&
|
|
632
|
-
journalOperation.matchedPreimage !== undefined
|
|
633
|
-
? [{
|
|
634
|
-
identity: journalOperation.rollbackTemporaryIdentity === undefined
|
|
635
|
-
? undefined
|
|
636
|
-
: deserializeKnownFileIdentity(journalOperation.rollbackTemporaryIdentity),
|
|
637
|
-
image: operation.precondition.acceptedPreimages[journalOperation.matchedPreimage],
|
|
638
|
-
kind: "rollback-temporary",
|
|
639
|
-
path: join(parent, `.${basename(operation.path)}.agent-teams.rollback.${options.operationIndex}.tmp`)
|
|
640
|
-
}]
|
|
641
|
-
: [])
|
|
642
|
-
];
|
|
643
|
-
for (const candidate of candidates) {
|
|
644
|
-
let observed;
|
|
645
|
-
try {
|
|
646
|
-
observed = await readBoundedRegularFile(candidate.path, candidate.image.size);
|
|
647
|
-
}
|
|
648
|
-
catch (error) {
|
|
649
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
throw error;
|
|
653
|
-
}
|
|
654
|
-
if (observed.outcome !== "read" || !matchesKnownFileImage({
|
|
655
|
-
state: "file", bytes: observed.bytes, identity: observed.identity,
|
|
656
|
-
mode: observed.mode & 0o777
|
|
657
|
-
}, candidate.image)) {
|
|
658
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Transaction temporary is foreign or modified: ${operation.path}.`);
|
|
659
|
-
}
|
|
660
|
-
if (candidate.identity === undefined && candidate.authorizedWithoutIdentity === true) {
|
|
661
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Authorized temporary exists without durable ownership identity: ${operation.path}.`);
|
|
662
|
-
}
|
|
663
|
-
if (candidate.identity === undefined ||
|
|
664
|
-
!sameKnownFileIdentity(observed.identity, candidate.identity)) {
|
|
665
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Transaction temporary identity is absent or changed: ${operation.path}.`);
|
|
666
|
-
}
|
|
667
|
-
await retireJournalBoundPath({
|
|
668
|
-
expectedIdentity: candidate.identity,
|
|
669
|
-
kind: candidate.kind,
|
|
670
|
-
operationIndex: options.operationIndex,
|
|
671
|
-
root: options.root,
|
|
672
|
-
store: options.store,
|
|
673
|
-
stored: options.stored
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async function cleanupAuthorizedCapture(options) {
|
|
678
|
-
const operation = options.journal.plan.operations[options.operationIndex];
|
|
679
|
-
const parent = dirname(join(options.root, ...operation.path.split("/")));
|
|
680
|
-
const paths = knownFileCapturePaths({
|
|
681
|
-
operationIndex: options.operationIndex,
|
|
682
|
-
operationPath: operation.path,
|
|
683
|
-
parent,
|
|
684
|
-
planDigest: options.journal.plan.planDigest
|
|
685
|
-
});
|
|
686
|
-
const metadata = await lstat(paths.directory).catch((error) => {
|
|
687
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
688
|
-
return null;
|
|
689
|
-
}
|
|
690
|
-
throw error;
|
|
691
|
-
});
|
|
692
|
-
if (metadata === null) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
696
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Authorized capture path is foreign: ${operation.path}.`);
|
|
697
|
-
}
|
|
698
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Authorized capture exists without durable ownership identity: ${operation.path}.`);
|
|
699
|
-
}
|
|
700
|
-
async function removeAuthorizedDirectories(root, directories) {
|
|
701
|
-
for (const repositoryPath of directories.toReversed()) {
|
|
702
|
-
const path = join(root, ...repositoryPath.split("/"));
|
|
703
|
-
const metadata = await lstat(path).catch((error) => {
|
|
704
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
707
|
-
throw error;
|
|
708
|
-
});
|
|
709
|
-
if (metadata === null) {
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
713
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Authorized directory path is foreign: ${repositoryPath}.`);
|
|
714
|
-
}
|
|
715
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Authorized directory exists without durable ownership identity: ${repositoryPath}.`);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
async function removeCreatedDirectories(root, directories) {
|
|
719
|
-
for (const directory of directories.toReversed()) {
|
|
720
|
-
const path = join(root, ...directory.path.split("/"));
|
|
721
|
-
let metadata;
|
|
722
|
-
try {
|
|
723
|
-
metadata = await lstat(path, { bigint: true });
|
|
724
|
-
}
|
|
725
|
-
catch (error) {
|
|
726
|
-
if (knownFileErrorCode(error) === "ENOENT") {
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
throw error;
|
|
730
|
-
}
|
|
731
|
-
const expected = deserializeKnownFileIdentity(directory.identity);
|
|
732
|
-
if (!metadata.isDirectory() || metadata.isSymbolicLink() ||
|
|
733
|
-
metadata.dev !== expected.dev || metadata.ino !== expected.ino ||
|
|
734
|
-
metadata.birthtimeNs !== expected.birthtimeNs) {
|
|
735
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Transaction-created directory changed before cleanup: ${directory.path}.`);
|
|
736
|
-
}
|
|
737
|
-
try {
|
|
738
|
-
await rmdir(path);
|
|
739
|
-
await syncDirectoryStrictly(dirname(path));
|
|
740
|
-
}
|
|
741
|
-
catch (error) {
|
|
742
|
-
if (knownFileErrorCode(error) !== "ENOTEMPTY") {
|
|
743
|
-
throw error;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
async function rollback(options) {
|
|
749
|
-
for (let index = options.stored.envelope.journal.operations.length - 1; index >= 0; index -= 1) {
|
|
750
|
-
let journalOperation = options.stored.envelope.journal.operations[index];
|
|
751
|
-
if (journalOperation.retirement !== undefined) {
|
|
752
|
-
await retireJournalBoundPath({
|
|
753
|
-
expectedIdentity: deserializeKnownFileIdentity(journalOperation.retirement.pathIdentity),
|
|
754
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
755
|
-
kind: journalOperation.retirement.kind,
|
|
756
|
-
operationIndex: index,
|
|
757
|
-
root: options.root,
|
|
758
|
-
store: options.store,
|
|
759
|
-
stored: options.stored
|
|
760
|
-
});
|
|
761
|
-
journalOperation = options.stored.envelope.journal.operations[index];
|
|
762
|
-
}
|
|
763
|
-
if (journalOperation.state === "capture-authorized") {
|
|
764
|
-
await cleanupAuthorizedCapture({
|
|
765
|
-
journal: options.stored.envelope.journal,
|
|
766
|
-
operationIndex: index,
|
|
767
|
-
root: options.root
|
|
768
|
-
});
|
|
769
|
-
continue;
|
|
770
|
-
}
|
|
771
|
-
if (![
|
|
772
|
-
"capture-ready", "preimage-captured", "destination-retired",
|
|
773
|
-
"publishing", "published", "rollback-restored"
|
|
774
|
-
].includes(journalOperation.state)) {
|
|
775
|
-
continue;
|
|
776
|
-
}
|
|
777
|
-
await restorePreimage({
|
|
778
|
-
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
779
|
-
operation: options.stored.envelope.journal.plan.operations[index],
|
|
780
|
-
operationIndex: index,
|
|
781
|
-
root: options.root,
|
|
782
|
-
store: options.store,
|
|
783
|
-
stored: options.stored
|
|
784
|
-
});
|
|
785
|
-
await cleanupOperationTemporaries({
|
|
786
|
-
operationIndex: index,
|
|
787
|
-
root: options.root,
|
|
788
|
-
store: options.store,
|
|
789
|
-
stored: options.stored
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
for (let index = options.stored.envelope.journal.operations.length - 1; index >= 0; index -= 1) {
|
|
793
|
-
const journal = options.stored.envelope.journal;
|
|
794
|
-
if (![
|
|
795
|
-
"capture-ready", "preimage-captured", "destination-retired",
|
|
796
|
-
"publishing", "published", "rollback-restored"
|
|
797
|
-
].includes(journal.operations[index].state)) {
|
|
798
|
-
await cleanupOperationTemporaries({
|
|
799
|
-
operationIndex: index,
|
|
800
|
-
root: options.root,
|
|
801
|
-
store: options.store,
|
|
802
|
-
stored: options.stored
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
const journal = options.stored.envelope.journal;
|
|
807
|
-
await removeCreatedDirectories(options.root, journal.createdDirectories);
|
|
808
|
-
await removeAuthorizedDirectories(options.root, journal.authorizedDirectories);
|
|
809
|
-
}
|
|
810
|
-
async function verifyRolledBackKnownFileState(root, journal) {
|
|
811
|
-
for (const [index, operation] of journal.plan.operations.entries()) {
|
|
812
|
-
const journalOperation = journal.operations[index];
|
|
813
|
-
const observed = await observeKnownFile(root, operation.path, maximumKnownFileEvidenceBytes(operation));
|
|
814
|
-
if (journalOperation.state === "already-satisfied") {
|
|
815
|
-
if (!matchesKnownFileImage(observed, operation.postimage)) {
|
|
816
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Unchanged guard drifted during rollback: ${operation.path}.`);
|
|
817
|
-
}
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
if (operation.precondition.state === "absent") {
|
|
821
|
-
if (observed.state !== "absent") {
|
|
822
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Absent preimage was not restored: ${operation.path}.`);
|
|
823
|
-
}
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
const matched = journalOperation.matchedPreimage;
|
|
827
|
-
if (matched === undefined ||
|
|
828
|
-
!matchesKnownFileImage(observed, operation.precondition.acceptedPreimages[matched])) {
|
|
829
|
-
throw new KnownFileTransactionError("KNOWN_FILE_RECOVERY_CONFLICT", `Exact preimage was not restored: ${operation.path}.`);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
3
|
+
import { classifyKnownFileRecoveryTransition } from "../../application/policies/classify-known-file-recovery-transition.js";
|
|
4
|
+
import { executeApplyingKnownFileRollback, executeCommittedKnownFileRecovery } from "./node-known-file-recovery-executor.js";
|
|
5
|
+
import { observeKnownFileRecoveryEvidence } from "./node-known-file-recovery-observation.js";
|
|
6
|
+
import { canonicalKnownFileRoot, KnownFileTransactionError } from "./node-known-file-transaction-filesystem.js";
|
|
7
|
+
import { releaseKnownFileTransactionLease } from "./node-known-file-transaction-lease-release.js";
|
|
833
8
|
export async function recoverKnownFileTransaction(options) {
|
|
834
9
|
if (process.platform === "win32") {
|
|
835
10
|
throw new KnownFileTransactionError("KNOWN_FILE_APPLY_UNSUPPORTED", "Known-file recovery requires strict directory durability and is not qualified on Windows.");
|
|
@@ -841,45 +16,39 @@ export async function recoverKnownFileTransaction(options) {
|
|
|
841
16
|
allowRecoveryOf: "known-file-transaction"
|
|
842
17
|
});
|
|
843
18
|
let retainBarrier = true;
|
|
19
|
+
let primaryFailure;
|
|
844
20
|
try {
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const [version, buildIdentity] = await Promise.all([
|
|
853
|
-
installedFoundationVersion(),
|
|
854
|
-
installedFoundationBuildIdentity()
|
|
855
|
-
]);
|
|
856
|
-
if (stored.envelope.foundation.version !== version ||
|
|
857
|
-
stored.envelope.foundation.buildIdentity !== buildIdentity) {
|
|
858
|
-
throw new KnownFileTransactionError("KNOWN_FILE_EXACT_BUILD_REQUIRED", "The exact Foundation build that created this journal must recover it.");
|
|
859
|
-
}
|
|
860
|
-
if (stored.envelope.state === "COMMITTED") {
|
|
861
|
-
await verifyCommittedKnownFilePostimages(root, stored.envelope.journal);
|
|
862
|
-
await cleanupCommittedKnownFileCaptures(root, stored.envelope.journal, options.faultInjector);
|
|
863
|
-
await verifyCommittedKnownFilePostimages(root, stored.envelope.journal);
|
|
864
|
-
const result = compileKnownFileTransactionReceipt(stored.envelope.journal, "applied");
|
|
865
|
-
await store.remove(stored.authority);
|
|
866
|
-
retainBarrier = false;
|
|
867
|
-
return result;
|
|
21
|
+
const evidence = await observeKnownFileRecoveryEvidence(root);
|
|
22
|
+
const transition = classifyKnownFileRecoveryTransition({
|
|
23
|
+
envelope: evidence.stored.envelope,
|
|
24
|
+
installedBuild: evidence.installedBuild
|
|
25
|
+
});
|
|
26
|
+
if (transition.action === "reject") {
|
|
27
|
+
throw new KnownFileTransactionError(transition.code, transition.message);
|
|
868
28
|
}
|
|
869
|
-
|
|
29
|
+
const execution = {
|
|
870
30
|
...(options.faultInjector === undefined ? {} : { faultInjector: options.faultInjector }),
|
|
871
31
|
root,
|
|
872
|
-
store,
|
|
873
|
-
stored
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
32
|
+
store: evidence.store,
|
|
33
|
+
stored: evidence.stored
|
|
34
|
+
};
|
|
35
|
+
const result = transition.action === "resume-committed-cleanup"
|
|
36
|
+
? await executeCommittedKnownFileRecovery(execution)
|
|
37
|
+
: await executeApplyingKnownFileRollback(execution);
|
|
878
38
|
retainBarrier = false;
|
|
879
39
|
return result;
|
|
880
40
|
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
primaryFailure = { reason: error };
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
881
45
|
finally {
|
|
882
|
-
await
|
|
46
|
+
await releaseKnownFileTransactionLease({
|
|
47
|
+
jointFailureMessage: "Known-file recovery and transaction lease release both failed.",
|
|
48
|
+
lease,
|
|
49
|
+
...(primaryFailure === undefined ? {} : { primaryFailure }),
|
|
50
|
+
retainTransactionBarrier: retainBarrier
|
|
51
|
+
});
|
|
883
52
|
if (!retainBarrier) {
|
|
884
53
|
await pruneFoundationStateDirectory(root);
|
|
885
54
|
}
|