mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 03:18:09 +08:00
fix(ql3): seal ordered reconciliation evidence
This commit is contained in:
+11
-3
@@ -86,6 +86,7 @@ interface AutomationProof {
|
|||||||
interface SecretConfigProof {
|
interface SecretConfigProof {
|
||||||
readonly intent: Readonly<LocalReconciliationSecretConfigApplyIntent>;
|
readonly intent: Readonly<LocalReconciliationSecretConfigApplyIntent>;
|
||||||
readonly receipt: Readonly<LocalReconciliationSecretConfigApplyReceipt>;
|
readonly receipt: Readonly<LocalReconciliationSecretConfigApplyReceipt>;
|
||||||
|
readonly preparedHeadDigest: string;
|
||||||
readonly paths: ReturnType<typeof localReconciliationSecretConfigApplyPaths>;
|
readonly paths: ReturnType<typeof localReconciliationSecretConfigApplyPaths>;
|
||||||
readonly storageState: 'applied' | 'completed';
|
readonly storageState: 'applied' | 'completed';
|
||||||
}
|
}
|
||||||
@@ -107,6 +108,7 @@ export interface LocalReconciliationCompletionDependencies
|
|||||||
async function runHistoryProof(
|
async function runHistoryProof(
|
||||||
command: Readonly<LocalReconciliationCompleteCommand>,
|
command: Readonly<LocalReconciliationCompleteCommand>,
|
||||||
terminal: Readonly<LocalReconciliationApplicationTerminal>,
|
terminal: Readonly<LocalReconciliationApplicationTerminal>,
|
||||||
|
secretConfig: Readonly<SecretConfigProof> | null,
|
||||||
uid: number,
|
uid: number,
|
||||||
dependencies: LocalReconciliationCompletionDependencies,
|
dependencies: LocalReconciliationCompletionDependencies,
|
||||||
): Promise<Readonly<RunHistoryProof> | null> {
|
): Promise<Readonly<RunHistoryProof> | null> {
|
||||||
@@ -153,7 +155,8 @@ async function runHistoryProof(
|
|||||||
command.request.runHistory.expectedPreservationDigest ||
|
command.request.runHistory.expectedPreservationDigest ||
|
||||||
history.receipt.applicationPlanDigest !==
|
history.receipt.applicationPlanDigest !==
|
||||||
terminal.plan.applicationPlanDigest ||
|
terminal.plan.applicationPlanDigest ||
|
||||||
history.receipt.sourceHeadDigest !== command.request.expectedHeadDigest
|
history.receipt.sourceHeadDigest !==
|
||||||
|
(secretConfig?.preparedHeadDigest ?? command.request.expectedHeadDigest)
|
||||||
) {
|
) {
|
||||||
fail('run history preservation evidence is detached');
|
fail('run history preservation evidence is detached');
|
||||||
}
|
}
|
||||||
@@ -571,6 +574,7 @@ async function secretConfigProof(
|
|||||||
selected,
|
selected,
|
||||||
uid,
|
uid,
|
||||||
);
|
);
|
||||||
|
const targetSnapshotSha256 = decision.context.planHeader.targetSnapshotSha256;
|
||||||
if (
|
if (
|
||||||
intent.command.options.deploymentRoot !== command.options.deploymentRoot ||
|
intent.command.options.deploymentRoot !== command.options.deploymentRoot ||
|
||||||
intent.command.options.applicationRoot !==
|
intent.command.options.applicationRoot !==
|
||||||
@@ -589,8 +593,9 @@ async function secretConfigProof(
|
|||||||
receipt.decisionId !== binding.decisionId ||
|
receipt.decisionId !== binding.decisionId ||
|
||||||
receipt.applyDigest !== binding.expectedApplyDigest ||
|
receipt.applyDigest !== binding.expectedApplyDigest ||
|
||||||
receipt.preparationDigest !== intent.preparationDigest ||
|
receipt.preparationDigest !== intent.preparationDigest ||
|
||||||
(automation !== null &&
|
(automation === null
|
||||||
intent.backup.sha256 !== automation.receipt.targetAfter.sha256) ||
|
? targetSnapshotSha256 !== null
|
||||||
|
: targetSnapshotSha256 !== automation.receipt.targetAfter.sha256) ||
|
||||||
fs.existsSync(selected.rollbackReceipt)
|
fs.existsSync(selected.rollbackReceipt)
|
||||||
) {
|
) {
|
||||||
fail('secret config apply evidence is detached');
|
fail('secret config apply evidence is detached');
|
||||||
@@ -623,6 +628,7 @@ async function secretConfigProof(
|
|||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
intent,
|
intent,
|
||||||
receipt,
|
receipt,
|
||||||
|
preparedHeadDigest: decision.context.planHeader.preparedHeadDigest,
|
||||||
paths: selected,
|
paths: selected,
|
||||||
storageState,
|
storageState,
|
||||||
});
|
});
|
||||||
@@ -820,6 +826,7 @@ export async function completeLocalReconciliation(
|
|||||||
const runHistory = await runHistoryProof(
|
const runHistory = await runHistoryProof(
|
||||||
command,
|
command,
|
||||||
terminal,
|
terminal,
|
||||||
|
secretConfig,
|
||||||
uid,
|
uid,
|
||||||
dependencies,
|
dependencies,
|
||||||
);
|
);
|
||||||
@@ -1015,6 +1022,7 @@ export async function verifyLocalReconciliationCompletion(
|
|||||||
const runHistory = await runHistoryProof(
|
const runHistory = await runHistoryProof(
|
||||||
syntheticCompleteCommand,
|
syntheticCompleteCommand,
|
||||||
terminal,
|
terminal,
|
||||||
|
secretConfig,
|
||||||
uid,
|
uid,
|
||||||
dependencies,
|
dependencies,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4402,6 +4402,66 @@ test('Secret/Config plan follows applied Automation and preserved Run History on
|
|||||||
);
|
);
|
||||||
assert.equal(applied.state, 'reconciliation_secret_config_applied');
|
assert.equal(applied.state, 'reconciliation_secret_config_applied');
|
||||||
assert.equal(applied.activeBindingCount, 1);
|
assert.equal(applied.activeBindingCount, 1);
|
||||||
|
|
||||||
|
const completionRoot = path.join(
|
||||||
|
path.dirname(state.captureRoot),
|
||||||
|
'cross-domain-completion',
|
||||||
|
);
|
||||||
|
fs.mkdirSync(completionRoot, { mode: 0o700 });
|
||||||
|
const completionCommand = {
|
||||||
|
schemaVersion: 3,
|
||||||
|
operation: 'local.deployment.reconciliation.complete',
|
||||||
|
options: {
|
||||||
|
deploymentRoot: state.deploymentRoot,
|
||||||
|
applicationRoot: state.applicationRoot,
|
||||||
|
completionRoot,
|
||||||
|
automation: {
|
||||||
|
automationRoot: state.automationRoot,
|
||||||
|
automationDecisionRoot: state.automationDecisionRoot,
|
||||||
|
automationApplyRoot: state.automationApplyRoot,
|
||||||
|
targetDatabasePath: state.targetDatabasePath,
|
||||||
|
},
|
||||||
|
secretConfig: {
|
||||||
|
secretConfigRoot,
|
||||||
|
secretConfigDecisionRoot,
|
||||||
|
secretConfigApplyRoot,
|
||||||
|
targetDatabasePath: state.targetDatabasePath,
|
||||||
|
},
|
||||||
|
runHistory: {
|
||||||
|
runHistoryRoot,
|
||||||
|
decisionFilePath: state.reviewFile.filePath,
|
||||||
|
},
|
||||||
|
allowRootService: rootAcknowledgement(),
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
completionId: '00000000-0000-4000-8000-000000000434',
|
||||||
|
applicationId: state.application.applicationId,
|
||||||
|
expectedApplicationPlanDigest: state.application.applicationPlanDigest,
|
||||||
|
expectedHeadDigest: applied.instanceHeadDigest,
|
||||||
|
automation: {
|
||||||
|
automationId: state.automationCommand.request.automationId,
|
||||||
|
decisionId: state.decisionId,
|
||||||
|
expectedApplyDigest: state.applied.applyDigest,
|
||||||
|
},
|
||||||
|
secretConfig: {
|
||||||
|
secretConfigId,
|
||||||
|
decisionId: secretConfigDecisionId,
|
||||||
|
expectedApplyDigest: applied.applyDigest,
|
||||||
|
},
|
||||||
|
runHistory: {
|
||||||
|
preservationId: preservationCommand.request.preservationId,
|
||||||
|
expectedPreservationDigest: preserved.preservationDigest,
|
||||||
|
},
|
||||||
|
completedAtMs: appliedAtMs + 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const completed = await completeLocalReconciliation(completionCommand);
|
||||||
|
assert.equal(completed.state, 'reconciliation_completed');
|
||||||
|
assert.equal(completed.adapterCount, 3);
|
||||||
|
assert.equal(
|
||||||
|
(await completeLocalReconciliation(completionCommand)).status,
|
||||||
|
'existing',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Secret/Config decision reauthenticates the same reviewer, seals exact candidates and verifies content-free', async (t) => {
|
test('Secret/Config decision reauthenticates the same reviewer, seals exact candidates and verifies content-free', async (t) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user