mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 08:05:22 +08:00
fix(ql3): seal ordered reconciliation evidence
This commit is contained in:
+11
-3
@@ -86,6 +86,7 @@ interface AutomationProof {
|
||||
interface SecretConfigProof {
|
||||
readonly intent: Readonly<LocalReconciliationSecretConfigApplyIntent>;
|
||||
readonly receipt: Readonly<LocalReconciliationSecretConfigApplyReceipt>;
|
||||
readonly preparedHeadDigest: string;
|
||||
readonly paths: ReturnType<typeof localReconciliationSecretConfigApplyPaths>;
|
||||
readonly storageState: 'applied' | 'completed';
|
||||
}
|
||||
@@ -107,6 +108,7 @@ export interface LocalReconciliationCompletionDependencies
|
||||
async function runHistoryProof(
|
||||
command: Readonly<LocalReconciliationCompleteCommand>,
|
||||
terminal: Readonly<LocalReconciliationApplicationTerminal>,
|
||||
secretConfig: Readonly<SecretConfigProof> | null,
|
||||
uid: number,
|
||||
dependencies: LocalReconciliationCompletionDependencies,
|
||||
): Promise<Readonly<RunHistoryProof> | null> {
|
||||
@@ -153,7 +155,8 @@ async function runHistoryProof(
|
||||
command.request.runHistory.expectedPreservationDigest ||
|
||||
history.receipt.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');
|
||||
}
|
||||
@@ -571,6 +574,7 @@ async function secretConfigProof(
|
||||
selected,
|
||||
uid,
|
||||
);
|
||||
const targetSnapshotSha256 = decision.context.planHeader.targetSnapshotSha256;
|
||||
if (
|
||||
intent.command.options.deploymentRoot !== command.options.deploymentRoot ||
|
||||
intent.command.options.applicationRoot !==
|
||||
@@ -589,8 +593,9 @@ async function secretConfigProof(
|
||||
receipt.decisionId !== binding.decisionId ||
|
||||
receipt.applyDigest !== binding.expectedApplyDigest ||
|
||||
receipt.preparationDigest !== intent.preparationDigest ||
|
||||
(automation !== null &&
|
||||
intent.backup.sha256 !== automation.receipt.targetAfter.sha256) ||
|
||||
(automation === null
|
||||
? targetSnapshotSha256 !== null
|
||||
: targetSnapshotSha256 !== automation.receipt.targetAfter.sha256) ||
|
||||
fs.existsSync(selected.rollbackReceipt)
|
||||
) {
|
||||
fail('secret config apply evidence is detached');
|
||||
@@ -623,6 +628,7 @@ async function secretConfigProof(
|
||||
return Object.freeze({
|
||||
intent,
|
||||
receipt,
|
||||
preparedHeadDigest: decision.context.planHeader.preparedHeadDigest,
|
||||
paths: selected,
|
||||
storageState,
|
||||
});
|
||||
@@ -820,6 +826,7 @@ export async function completeLocalReconciliation(
|
||||
const runHistory = await runHistoryProof(
|
||||
command,
|
||||
terminal,
|
||||
secretConfig,
|
||||
uid,
|
||||
dependencies,
|
||||
);
|
||||
@@ -1015,6 +1022,7 @@ export async function verifyLocalReconciliationCompletion(
|
||||
const runHistory = await runHistoryProof(
|
||||
syntheticCompleteCommand,
|
||||
terminal,
|
||||
secretConfig,
|
||||
uid,
|
||||
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.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) => {
|
||||
|
||||
Reference in New Issue
Block a user