mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:02:22 +08:00
fix(ql3): preserve target-native reconciliation domains
This commit is contained in:
@@ -74,17 +74,6 @@ const {
|
||||
const {
|
||||
targetStoppedEvidence,
|
||||
} = require('../dist/deployment/cutover/targetStopRecordEvidence.js');
|
||||
const {
|
||||
collectLocalReconciliationSecretConfigCompletedStorage,
|
||||
localReconciliationSecretConfigApplyPaths,
|
||||
readLocalReconciliationSecretConfigApplyIntent,
|
||||
validateLocalReconciliationSecretConfigCompletedStorage,
|
||||
} = require('../dist/deployment/reconciliation/application/secret-and-config/application/storage.js');
|
||||
const {
|
||||
buildLocalReconciliationCompletionReceipt,
|
||||
normalizeLocalReconciliationCompletionReceipt,
|
||||
} = require('../dist/deployment/reconciliation/completion/evidence.js');
|
||||
|
||||
function digest(value) {
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
@@ -2974,6 +2963,55 @@ test('review diagnostics keep secret and unknown facts blocked and row-free', (t
|
||||
);
|
||||
});
|
||||
|
||||
test('review diagnostics preserve target-native identity but keep legacy identity fail-closed', (t) => {
|
||||
const initializeDatabases = (paths) => {
|
||||
planningDatabaseInitializer()(paths);
|
||||
const legacy = new DatabaseSync(paths.legacySourcePath);
|
||||
legacy.exec('CREATE TABLE "Auths" (id INTEGER PRIMARY KEY)');
|
||||
legacy.close();
|
||||
fs.copyFileSync(paths.legacySourcePath, paths.recoveryPath);
|
||||
fs.chmodSync(paths.recoveryPath, 0o600);
|
||||
const target = new DatabaseSync(paths.targetDatabasePath);
|
||||
target.exec(
|
||||
'CREATE TABLE "QingLong3IdentityRecords" (id INTEGER PRIMARY KEY)',
|
||||
);
|
||||
target.close();
|
||||
};
|
||||
const state = preparedReview(t, {
|
||||
initializeDatabases,
|
||||
planId: '00000000-0000-4000-8000-000000000333',
|
||||
reviewId: '00000000-0000-4000-8000-000000000334',
|
||||
reviewSuffix: 'identity-custody',
|
||||
});
|
||||
assert.equal(state.planned.outcome, 'manual_required');
|
||||
const prepared = prepareLocalReconciliationReview(state.reviewCommand);
|
||||
const legacyCommand = diagnosticCommand(state, prepared, {
|
||||
database: 'legacy',
|
||||
domain: 'identity_policy_audit',
|
||||
outputName: 'legacy-identity.json',
|
||||
});
|
||||
writeLocalReconciliationReviewDiagnostics(legacyCommand);
|
||||
const legacy = JSON.parse(
|
||||
fs.readFileSync(legacyCommand.request.outputPath, 'utf8'),
|
||||
);
|
||||
assert.equal(legacy.records[0].name, 'Auths');
|
||||
assert.equal(legacy.records[0].decisionRequirement, 'blocked');
|
||||
assert.equal(legacy.records[0].reason, 'identity_custody_required');
|
||||
|
||||
const targetCommand = diagnosticCommand(state, prepared, {
|
||||
database: 'target',
|
||||
domain: 'identity_policy_audit',
|
||||
outputName: 'target-identity.json',
|
||||
});
|
||||
writeLocalReconciliationReviewDiagnostics(targetCommand);
|
||||
const target = JSON.parse(
|
||||
fs.readFileSync(targetCommand.request.outputPath, 'utf8'),
|
||||
);
|
||||
assert.equal(target.records[0].name, 'QingLong3IdentityRecords');
|
||||
assert.equal(target.records[0].decisionRequirement, 'required');
|
||||
assert.equal(target.records[0].reason, 'reviewable_fact');
|
||||
});
|
||||
|
||||
test('review diagnostics page at sixty-four and CLI output stays content-free', (t) => {
|
||||
const initializeDatabases = (paths) => {
|
||||
planningDatabaseInitializer()(paths);
|
||||
@@ -4352,7 +4390,7 @@ test('Secret/Config apply publishes encrypted material atomically and recovers e
|
||||
assert.equal(databaseCloses, authentications);
|
||||
});
|
||||
|
||||
test('completion v3 proves Secret/Config apply but preserves rollback authority while other domains remain manual', async (t) => {
|
||||
test('completion v3 completes a classified v52 target and collects Secret/Config rollback authority', async (t) => {
|
||||
const state = await appliedSecretConfigFixture(t, {
|
||||
suffix: 'completion-v3',
|
||||
});
|
||||
@@ -4402,116 +4440,71 @@ test('completion v3 proves Secret/Config apply but preserves rollback authority
|
||||
const backupPath = path.join(backupRoot, 'before.sqlite');
|
||||
const materialPath = path.join(applyRoot, 'materials.ndjson');
|
||||
assert.equal(fs.existsSync(backupPath), true);
|
||||
|
||||
await assert.rejects(
|
||||
completeLocalReconciliation(command),
|
||||
/identity_policy_audit is not terminally reconciled/,
|
||||
const plan = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(
|
||||
state.planRoot,
|
||||
state.prepareCommand.request.planId,
|
||||
'plan.json',
|
||||
),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
assert.equal(fs.existsSync(backupPath), true);
|
||||
assert.deepEqual(
|
||||
plan.domains.find((domain) => domain.domain === 'unknown'),
|
||||
{
|
||||
domain: 'unknown',
|
||||
legacySchemaObjects: 0,
|
||||
targetSchemaObjects: 0,
|
||||
legacyTables: 0,
|
||||
targetTables: 0,
|
||||
legacyRows: 0,
|
||||
targetRows: 0,
|
||||
rowCountsComplete: true,
|
||||
inventoryDigest: plan.domains.find(
|
||||
(domain) => domain.domain === 'unknown',
|
||||
).inventoryDigest,
|
||||
disposition: 'aligned',
|
||||
},
|
||||
);
|
||||
assert.equal(
|
||||
plan.domains.find((domain) => domain.domain === 'identity_policy_audit')
|
||||
.disposition,
|
||||
'target_only',
|
||||
);
|
||||
const targetIdentityDecisions = state.reviewFile.decisions.filter(
|
||||
(decision) =>
|
||||
decision.database === 'target' &&
|
||||
decision.domain === 'identity_policy_audit',
|
||||
);
|
||||
assert.ok(targetIdentityDecisions.length > 0);
|
||||
assert.equal(
|
||||
targetIdentityDecisions.every(
|
||||
(decision) => decision.disposition === 'retain_target',
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
const completed = await completeLocalReconciliation(command);
|
||||
assert.equal(completed.status, 'completed');
|
||||
assert.equal(completed.state, 'reconciliation_completed');
|
||||
assert.equal(completed.adapterCount, 1);
|
||||
assert.equal((await completeLocalReconciliation(command)).status, 'existing');
|
||||
assert.equal(
|
||||
readLocalCutoverInstanceHead(
|
||||
state.deploymentRoot,
|
||||
state.captureCommand.request.instanceId,
|
||||
state.uid,
|
||||
).state,
|
||||
'reconciliation_secret_config_applied',
|
||||
);
|
||||
assert.equal(
|
||||
fs.existsSync(path.join(completionRoot, command.request.completionId)),
|
||||
false,
|
||||
);
|
||||
|
||||
const domainNames = [
|
||||
'schema_lineage',
|
||||
'automation',
|
||||
'secret_and_config',
|
||||
'run_history',
|
||||
'plugin_package',
|
||||
'ai_and_tool',
|
||||
'identity_policy_audit',
|
||||
'unknown',
|
||||
];
|
||||
const domains = domainNames.map((domain) =>
|
||||
domain === 'secret_and_config'
|
||||
? {
|
||||
domain,
|
||||
action: 'adapter_required',
|
||||
evidenceKind: 'secret_config_application',
|
||||
evidenceDigest: state.applied.applyDigest,
|
||||
}
|
||||
: {
|
||||
domain,
|
||||
action: 'no_effect',
|
||||
evidenceKind: 'application_summary',
|
||||
evidenceDigest: 'a'.repeat(64),
|
||||
},
|
||||
);
|
||||
const receipt = buildLocalReconciliationCompletionReceipt({
|
||||
completionId: command.request.completionId,
|
||||
applicationId: command.request.applicationId,
|
||||
profile: state.captureCommand.request.profile,
|
||||
instanceId: state.captureCommand.request.instanceId,
|
||||
cutoverId: state.captureCommand.request.cutoverId,
|
||||
generation: 1,
|
||||
activationDigest: state.captureCommand.request.expectedActivationDigest,
|
||||
applicationPlanDigest: state.application.applicationPlanDigest,
|
||||
sourceHeadDigest: state.applied.instanceHeadDigest,
|
||||
domains,
|
||||
adapterCount: 1,
|
||||
completedAtMs: command.request.completedAtMs,
|
||||
});
|
||||
assert.equal(receipt.schemaVersion, 3);
|
||||
assert.deepEqual(
|
||||
normalizeLocalReconciliationCompletionReceipt(receipt),
|
||||
receipt,
|
||||
);
|
||||
|
||||
const currentHead = readLocalCutoverInstanceHead(
|
||||
state.deploymentRoot,
|
||||
state.captureCommand.request.instanceId,
|
||||
state.uid,
|
||||
);
|
||||
const durableHead = advanceLocalCutoverInstanceHead(
|
||||
{
|
||||
options: { deploymentRoot: state.deploymentRoot },
|
||||
request: {
|
||||
cutoverId: state.captureCommand.request.cutoverId,
|
||||
profile: state.captureCommand.request.profile,
|
||||
instanceId: state.captureCommand.request.instanceId,
|
||||
expectedActivationDigest:
|
||||
state.captureCommand.request.expectedActivationDigest,
|
||||
requestedAtMs: command.request.completedAtMs,
|
||||
},
|
||||
},
|
||||
state.uid,
|
||||
'reconciliation_completed',
|
||||
currentHead.generation,
|
||||
receipt.completionDigest,
|
||||
);
|
||||
assert.equal(durableHead.state, 'reconciliation_completed');
|
||||
const selected = localReconciliationSecretConfigApplyPaths(
|
||||
state.secretConfigApplyRoot,
|
||||
state.secretConfigId,
|
||||
);
|
||||
const intent = readLocalReconciliationSecretConfigApplyIntent(
|
||||
selected,
|
||||
state.uid,
|
||||
);
|
||||
collectLocalReconciliationSecretConfigCompletedStorage(
|
||||
selected,
|
||||
intent,
|
||||
state.uid,
|
||||
);
|
||||
collectLocalReconciliationSecretConfigCompletedStorage(
|
||||
selected,
|
||||
intent,
|
||||
state.uid,
|
||||
);
|
||||
validateLocalReconciliationSecretConfigCompletedStorage(
|
||||
selected,
|
||||
intent,
|
||||
state.uid,
|
||||
const receipt = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(completionRoot, command.request.completionId, 'receipt.json'),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
assert.equal(receipt.schemaVersion, 3);
|
||||
assert.equal(fs.existsSync(backupPath), false);
|
||||
assert.deepEqual(fs.readdirSync(backupRoot), []);
|
||||
assert.deepEqual(fs.readdirSync(path.join(applyRoot, 'rollback-work')), []);
|
||||
@@ -4527,6 +4520,26 @@ test('completion v3 proves Secret/Config apply but preserves rollback authority
|
||||
evidenceDigest: state.applied.applyDigest,
|
||||
},
|
||||
);
|
||||
for (const domainName of ['identity_policy_audit', 'unknown']) {
|
||||
assert.equal(
|
||||
receipt.domains.find((domain) => domain.domain === domainName).action,
|
||||
'no_effect',
|
||||
);
|
||||
}
|
||||
const verified = await verifyLocalReconciliationCompletion({
|
||||
schemaVersion: 3,
|
||||
operation: 'local.deployment.reconciliation.complete.verify',
|
||||
options: command.options,
|
||||
request: {
|
||||
completionId: command.request.completionId,
|
||||
applicationId: command.request.applicationId,
|
||||
expectedCompletionDigest: completed.completionDigest,
|
||||
automation: null,
|
||||
secretConfig,
|
||||
runHistory: null,
|
||||
},
|
||||
});
|
||||
assert.equal(verified.status, 'verified');
|
||||
});
|
||||
|
||||
test('completion v3 rejects rolled-back Secret/Config evidence', async (t) => {
|
||||
|
||||
Reference in New Issue
Block a user