mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const {
|
||||
chmodSync,
|
||||
mkdtempSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const { createHash, generateKeyPairSync, sign } = require('node:crypto');
|
||||
const { afterEach, test } = require('node:test');
|
||||
|
||||
const {
|
||||
createPluginPackagePromptOutputArtifact,
|
||||
} = require('../../ql3-ai/dist/prompt-output/pluginPackagePromptOutputArtifact.js');
|
||||
const {
|
||||
createPluginPackagePromptOutputExternalCustodyReceipt,
|
||||
} = require('../../ql3-ai/dist/prompt-output/custody/pluginPackagePromptOutputExternalCustody.js');
|
||||
const {
|
||||
createPluginPackagePromptOutputExternalCustodyBundle,
|
||||
} = require('../../ql3-ai/dist/prompt-output/custody/pluginPackagePromptOutputExternalCustodyBundle.js');
|
||||
const {
|
||||
createPluginPackagePromptOutputExternalRecoveryAuthorization,
|
||||
} = require('../../ql3-ai/dist/prompt-output/custody/pluginPackagePromptOutputExternalRecoveryAuthorization.js');
|
||||
const {
|
||||
pluginPackagePromptOutputKeyRotationMaterialProof,
|
||||
} = require('../../ql3-ai/dist/prompt-output/key-management/pluginPackagePromptOutputKeyRotation.js');
|
||||
const {
|
||||
disposeClusterPromptOutputExternalRecoveryInput,
|
||||
readClusterPromptOutputExternalRecoveryCommand,
|
||||
readClusterPromptOutputExternalRecoveryInput,
|
||||
} = require('../dist/prompt-output/external-recovery/promptOutputExternalRecoveryInput.js');
|
||||
const {
|
||||
ClusterPromptOutputExternalRecoveryVerifierConfigError,
|
||||
runClusterPromptOutputExternalRecoveryVerifier,
|
||||
} = require('../dist/prompt-output/external-recovery/promptOutputExternalRecoveryVerifier.js');
|
||||
|
||||
const temporaryDirectories = [];
|
||||
afterEach(() => {
|
||||
for (const directory of temporaryDirectories.splice(0)) {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function privateFile(directory, name, value, mode = 0o440) {
|
||||
const target = path.join(directory, name);
|
||||
writeFileSync(target, value, { mode: 0o600 });
|
||||
chmodSync(target, mode);
|
||||
return target;
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
const directory = mkdtempSync(path.join(os.tmpdir(), 'ql3-recovery-'));
|
||||
temporaryDirectories.push(directory);
|
||||
const now = Date.now();
|
||||
const material = Buffer.alloc(32, 0x5c);
|
||||
const wrapped = Buffer.from('provider-wrapped-key-material');
|
||||
const custodyKeys = generateKeyPairSync('ed25519');
|
||||
const approverAKeys = generateKeyPairSync('ed25519');
|
||||
const approverBKeys = generateKeyPairSync('ed25519');
|
||||
const catalogDigest = '1'.repeat(64);
|
||||
const artifact = createPluginPackagePromptOutputArtifact(
|
||||
{
|
||||
projectId: 'project-offline-recovery',
|
||||
runId: 'run-offline-recovery',
|
||||
stepRunId: 'step-offline-recovery',
|
||||
invocationId: 'invocation-offline-recovery',
|
||||
requestedBy: { type: 'user', id: 'requester-user' },
|
||||
result: {
|
||||
provider: 'openai-compatible',
|
||||
model: 'bounded-model',
|
||||
text: 'private offline recovery output',
|
||||
finishReason: 'stop',
|
||||
usage: {
|
||||
inputTokens: 4,
|
||||
outputTokens: 5,
|
||||
totalTokens: 9,
|
||||
costMicros: 10,
|
||||
},
|
||||
},
|
||||
retentionPolicy: {
|
||||
revision: 'offline-recovery-v1',
|
||||
retentionMs: 86_400_000,
|
||||
},
|
||||
keyId: 'offline-recovery-key',
|
||||
key: Buffer.from(material),
|
||||
sealedAtMs: now - 60_000,
|
||||
},
|
||||
() => Buffer.alloc(12, 0x39),
|
||||
);
|
||||
const receipt = createPluginPackagePromptOutputExternalCustodyReceipt(
|
||||
{
|
||||
custodyId: 'offline-custody',
|
||||
keyId: 'offline-recovery-key',
|
||||
materialProof: pluginPackagePromptOutputKeyRotationMaterialProof(
|
||||
'offline-recovery-key',
|
||||
material,
|
||||
),
|
||||
sourceGeneration: 4,
|
||||
sourceCatalogDigest: catalogDigest,
|
||||
wrappingProvider: 'external-kms',
|
||||
wrappingKeyRefDigest: '2'.repeat(64),
|
||||
wrappedMaterialDigest: createHash('sha256').update(wrapped).digest('hex'),
|
||||
wrappedMaterialBytes: wrapped.length,
|
||||
createdAtMs: now - 120_000,
|
||||
},
|
||||
{
|
||||
publicKey: custodyKeys.publicKey,
|
||||
sign: (message) => sign(null, message, custodyKeys.privateKey),
|
||||
},
|
||||
);
|
||||
const custodyBundle = createPluginPackagePromptOutputExternalCustodyBundle(
|
||||
receipt,
|
||||
custodyKeys.publicKey,
|
||||
wrapped,
|
||||
);
|
||||
const approvalSigner = (userId, authenticationId, keys, approvedAtMs) => ({
|
||||
userId,
|
||||
authenticationId,
|
||||
authenticatedAtMs: approvedAtMs - 1_000,
|
||||
approvedAtMs,
|
||||
publicKey: keys.publicKey,
|
||||
sign: (message) => sign(null, message, keys.privateKey),
|
||||
});
|
||||
const authorization =
|
||||
createPluginPackagePromptOutputExternalRecoveryAuthorization(
|
||||
{
|
||||
recoveryId: 'offline-recovery-001',
|
||||
requestId: 'offline-request-001',
|
||||
custodyId: receipt.custodyId,
|
||||
custodyReceiptDigest: receipt.receiptDigest,
|
||||
keyId: receipt.keyId,
|
||||
artifactId: artifact.artifactId,
|
||||
artifactDigest: artifact.artifactDigest,
|
||||
policyDigest: '3'.repeat(64),
|
||||
requestedBy: {
|
||||
userId: 'requester-user',
|
||||
authenticationId: 'requester-auth',
|
||||
authenticatedAtMs: now - 4_000,
|
||||
},
|
||||
requestedAtMs: now - 3_000,
|
||||
expiresAtMs: now + 10 * 60_000,
|
||||
},
|
||||
[
|
||||
approvalSigner(
|
||||
'reviewer-a',
|
||||
'reviewer-a-auth',
|
||||
approverAKeys,
|
||||
now - 2_000,
|
||||
),
|
||||
approvalSigner(
|
||||
'reviewer-b',
|
||||
'reviewer-b-auth',
|
||||
approverBKeys,
|
||||
now - 1_000,
|
||||
),
|
||||
],
|
||||
);
|
||||
const files = {
|
||||
authorizationFile: privateFile(
|
||||
directory,
|
||||
'authorization.json',
|
||||
JSON.stringify(authorization),
|
||||
),
|
||||
custodyBundleFile: privateFile(
|
||||
directory,
|
||||
'custody-bundle.json',
|
||||
JSON.stringify(custodyBundle),
|
||||
),
|
||||
recoveredMaterialFile: privateFile(directory, 'material.bin', material),
|
||||
durableKeyFactFile: privateFile(
|
||||
directory,
|
||||
'durable-fact.json',
|
||||
JSON.stringify({
|
||||
keyId: receipt.keyId,
|
||||
materialProof: receipt.materialProof,
|
||||
catalogDigest: receipt.sourceCatalogDigest,
|
||||
}),
|
||||
),
|
||||
artifactFile: privateFile(
|
||||
directory,
|
||||
'artifact.json',
|
||||
JSON.stringify(artifact),
|
||||
),
|
||||
custodyPublicKeyFile: privateFile(
|
||||
directory,
|
||||
'custody-public.pem',
|
||||
custodyKeys.publicKey.export({ format: 'pem', type: 'spki' }),
|
||||
),
|
||||
approverAFile: privateFile(
|
||||
directory,
|
||||
'reviewer-a-public.pem',
|
||||
approverAKeys.publicKey.export({ format: 'pem', type: 'spki' }),
|
||||
),
|
||||
approverBFile: privateFile(
|
||||
directory,
|
||||
'reviewer-b-public.pem',
|
||||
approverBKeys.publicKey.export({ format: 'pem', type: 'spki' }),
|
||||
),
|
||||
};
|
||||
const command = {
|
||||
schemaVersion: 1,
|
||||
operation: 'cluster.prompt-output-key.verify-recovery',
|
||||
authorizationFile: files.authorizationFile,
|
||||
custodyBundleFile: files.custodyBundleFile,
|
||||
recoveredMaterialFile: files.recoveredMaterialFile,
|
||||
durableKeyFactFile: files.durableKeyFactFile,
|
||||
artifactFile: files.artifactFile,
|
||||
custodyPublicKeyFile: files.custodyPublicKeyFile,
|
||||
approverPublicKeyFiles: [
|
||||
{ userId: 'reviewer-b', filePath: files.approverBFile },
|
||||
{ userId: 'reviewer-a', filePath: files.approverAFile },
|
||||
],
|
||||
};
|
||||
const commandFile = privateFile(
|
||||
directory,
|
||||
'command.json',
|
||||
JSON.stringify(command),
|
||||
0o444,
|
||||
);
|
||||
return { directory, now, files, command, commandFile };
|
||||
}
|
||||
|
||||
test('reads one private recovery workspace and emits a content-free proof', () => {
|
||||
const value = fixture();
|
||||
const command = readClusterPromptOutputExternalRecoveryCommand(
|
||||
value.commandFile,
|
||||
);
|
||||
assert.deepEqual(
|
||||
command.approverPublicKeyFiles.map(({ userId }) => userId),
|
||||
['reviewer-a', 'reviewer-b'],
|
||||
);
|
||||
const input = readClusterPromptOutputExternalRecoveryInput(command);
|
||||
const proof = runClusterPromptOutputExternalRecoveryVerifier(
|
||||
input,
|
||||
value.now + 3_000,
|
||||
);
|
||||
assert.equal(proof.schema.includes('authorized'), true);
|
||||
assert.equal(proof.authorizationDigest.length, 64);
|
||||
assert.equal(JSON.stringify(proof).includes('private offline'), false);
|
||||
disposeClusterPromptOutputExternalRecoveryInput(input);
|
||||
assert.equal(
|
||||
input.material.every((byte) => byte === 0),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
input.wrappedMaterial.every((byte) => byte === 0),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('CLI verifies the workspace without database, Kubernetes or KMS input', () => {
|
||||
const value = fixture();
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../dist/prompt-output/external-recovery/promptOutputExternalRecoveryCli.js',
|
||||
),
|
||||
'run',
|
||||
'--command-file',
|
||||
value.commandFile,
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
const report = JSON.parse(result.stdout);
|
||||
assert.equal(report.event, 'recovery_verified');
|
||||
assert.equal(report.proofDigest.length, 64);
|
||||
assert.equal(result.stdout.includes('private offline'), false);
|
||||
});
|
||||
|
||||
test('rejects writable or symlinked recovery material and custody bundles', () => {
|
||||
for (const mutation of [
|
||||
(value) => chmodSync(value.files.recoveredMaterialFile, 0o640),
|
||||
(value) => chmodSync(value.files.custodyBundleFile, 0o640),
|
||||
(value) => {
|
||||
const link = path.join(value.directory, 'material-link.bin');
|
||||
symlinkSync(value.files.recoveredMaterialFile, link);
|
||||
const command = {
|
||||
...value.command,
|
||||
recoveredMaterialFile: link,
|
||||
};
|
||||
chmodSync(value.commandFile, 0o600);
|
||||
writeFileSync(value.commandFile, JSON.stringify(command));
|
||||
chmodSync(value.commandFile, 0o444);
|
||||
},
|
||||
(value) => {
|
||||
const link = path.join(value.directory, 'custody-bundle-link.json');
|
||||
symlinkSync(value.files.custodyBundleFile, link);
|
||||
const command = {
|
||||
...value.command,
|
||||
custodyBundleFile: link,
|
||||
};
|
||||
chmodSync(value.commandFile, 0o600);
|
||||
writeFileSync(value.commandFile, JSON.stringify(command));
|
||||
chmodSync(value.commandFile, 0o444);
|
||||
},
|
||||
]) {
|
||||
const value = fixture();
|
||||
mutation(value);
|
||||
const command = readClusterPromptOutputExternalRecoveryCommand(
|
||||
value.commandFile,
|
||||
);
|
||||
assert.throws(() => readClusterPromptOutputExternalRecoveryInput(command));
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects authorization and Artifact drift with a content-free error', () => {
|
||||
const value = fixture();
|
||||
const command = readClusterPromptOutputExternalRecoveryCommand(
|
||||
value.commandFile,
|
||||
);
|
||||
const input = readClusterPromptOutputExternalRecoveryInput(command);
|
||||
try {
|
||||
assert.throws(
|
||||
() =>
|
||||
runClusterPromptOutputExternalRecoveryVerifier(
|
||||
{
|
||||
...input,
|
||||
artifact: {
|
||||
...input.artifact,
|
||||
artifactDigest: '4'.repeat(64),
|
||||
},
|
||||
},
|
||||
value.now + 3_000,
|
||||
),
|
||||
ClusterPromptOutputExternalRecoveryVerifierConfigError,
|
||||
);
|
||||
} finally {
|
||||
disposeClusterPromptOutputExternalRecoveryInput(input);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user