mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
InvalidPluginPackagePromptExecutionPlanError,
|
||||
PLUGIN_PACKAGE_PROMPT_EXECUTION_PLAN_SCHEMA,
|
||||
createPluginPackagePromptAdmissionBundle,
|
||||
normalizePluginPackagePromptExecutionPlan,
|
||||
pluginPackagePromptExecutionPlanDigest,
|
||||
preparePluginPackagePromptExecution,
|
||||
} = require('../dist/prompt/pluginPackagePromptExecution.js');
|
||||
const {
|
||||
pluginPackageAutomationPublicationDigest,
|
||||
} = require('@qinglong/runtime-core/plugin-package-automation-publication');
|
||||
|
||||
function publication(overrides = {}) {
|
||||
const unsigned = {
|
||||
schema: 'qinglong/plugin-package-automation-publication@v1',
|
||||
target: {
|
||||
projectId: 'project-a',
|
||||
packageName: 'package-a',
|
||||
installationId: 'installation-a',
|
||||
lockDigest: '1'.repeat(64),
|
||||
generation: 3,
|
||||
generationDigest: '2'.repeat(64),
|
||||
materializedRevisionDigest: '3'.repeat(64),
|
||||
},
|
||||
state: 'active',
|
||||
version: 1,
|
||||
previousPublicationDigest: null,
|
||||
lifecycleEventDigest: null,
|
||||
definitions: {
|
||||
workflows: [],
|
||||
prompts: [
|
||||
{
|
||||
schema: 'qinglong/plugin-package-prompt-resource@v1',
|
||||
id: 'summary',
|
||||
name: 'Summary',
|
||||
template: 'Summarize {{subject}} for {{audience}}.',
|
||||
parameters: [
|
||||
{ name: 'audience', required: false },
|
||||
{ name: 'subject', required: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
publishedAtMs: 1_000,
|
||||
...overrides,
|
||||
};
|
||||
return {
|
||||
...unsigned,
|
||||
publicationDigest: pluginPackageAutomationPublicationDigest(unsigned),
|
||||
};
|
||||
}
|
||||
|
||||
function prepare(overrides = {}) {
|
||||
const active = publication();
|
||||
return preparePluginPackagePromptExecution({
|
||||
publication: active,
|
||||
expectedPublicationDigest: active.publicationDigest,
|
||||
promptId: 'summary',
|
||||
requestId: 'prompt-request-a',
|
||||
traceId: 'trace-a',
|
||||
requestedBySubject: { type: 'user', id: 'user-a' },
|
||||
policyFence: { projectVersion: 1, bindingVersion: 1 },
|
||||
parameters: { subject: 'QingLong 3.0' },
|
||||
provider: 'openai-compatible',
|
||||
model: 'model-a',
|
||||
maxOutputTokens: 512,
|
||||
temperature: 0.2,
|
||||
plannedAtMs: 2_000,
|
||||
deadlineAtMs: 62_000,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
test('prepares a content-free immutable Prompt execution plan', () => {
|
||||
const first = prepare();
|
||||
const second = prepare();
|
||||
assert.deepEqual(first, second);
|
||||
assert.equal(first.plan.schema, PLUGIN_PACKAGE_PROMPT_EXECUTION_PLAN_SCHEMA);
|
||||
assert.deepEqual(first.plan.output, { mode: 'live_only' });
|
||||
assert.equal(
|
||||
first.request.messages[0].content,
|
||||
'Summarize QingLong 3.0 for .',
|
||||
);
|
||||
assert.match(first.plan.invocationId, /^ppi:[0-9a-f]{32}$/);
|
||||
assert.match(first.plan.runId, /^ppr:[0-9a-f]{32}$/);
|
||||
assert.match(first.plan.stepRunId, /^pps:[0-9a-f]{32}$/);
|
||||
assert.match(first.plan.modelRequestDigest, /^sha256:[0-9a-f]{64}$/);
|
||||
const durable = JSON.stringify(first.plan);
|
||||
assert.equal(durable.includes('QingLong 3.0'), false);
|
||||
assert.equal(durable.includes('Summarize '), false);
|
||||
assert.equal(Object.isFrozen(first.plan), true);
|
||||
});
|
||||
|
||||
test('binds explicit durable output retention and preserves legacy live-only replay', () => {
|
||||
const durable = prepare({
|
||||
output: {
|
||||
mode: 'durable_artifact',
|
||||
retentionPolicy: {
|
||||
revision: 'edge-output-v1',
|
||||
retentionMs: 86_400_000,
|
||||
},
|
||||
},
|
||||
}).plan;
|
||||
assert.equal(durable.output.mode, 'durable_artifact');
|
||||
assert.match(durable.output.retentionPolicyDigest, /^[0-9a-f]{64}$/);
|
||||
assert.notEqual(durable.planDigest, prepare().plan.planDigest);
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackagePromptExecutionPlan({
|
||||
...durable,
|
||||
output: {
|
||||
...durable.output,
|
||||
retentionPolicyDigest: 'f'.repeat(64),
|
||||
},
|
||||
}),
|
||||
InvalidPluginPackagePromptExecutionPlanError,
|
||||
);
|
||||
|
||||
const current = prepare().plan;
|
||||
const {
|
||||
output: _output,
|
||||
planDigest: _planDigest,
|
||||
...legacyUnsigned
|
||||
} = current;
|
||||
const legacy = {
|
||||
...legacyUnsigned,
|
||||
planDigest: pluginPackagePromptExecutionPlanDigest(legacyUnsigned),
|
||||
};
|
||||
assert.equal(
|
||||
normalizePluginPackagePromptExecutionPlan(legacy).output,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test('binds parameter presence and content without recursively rendering values', () => {
|
||||
const omitted = prepare();
|
||||
const empty = prepare({
|
||||
parameters: { subject: 'QingLong 3.0', audience: '' },
|
||||
});
|
||||
const literal = prepare({
|
||||
parameters: { subject: '{{audience}}', audience: 'operators' },
|
||||
});
|
||||
assert.notEqual(omitted.plan.parameterDigest, empty.plan.parameterDigest);
|
||||
assert.equal(
|
||||
literal.request.messages[0].content,
|
||||
'Summarize {{audience}} for operators.',
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects stale publication, missing or widened parameters and unsafe budgets', () => {
|
||||
const active = publication();
|
||||
assert.throws(
|
||||
() =>
|
||||
preparePluginPackagePromptExecution({
|
||||
...prepare().plan,
|
||||
publication: active,
|
||||
expectedPublicationDigest: 'f'.repeat(64),
|
||||
promptId: 'summary',
|
||||
requestId: 'request-b',
|
||||
traceId: 'trace-b',
|
||||
requestedBySubject: { type: 'user', id: 'user-b' },
|
||||
policyFence: { projectVersion: 1, bindingVersion: 1 },
|
||||
parameters: { subject: 'x' },
|
||||
provider: 'provider-a',
|
||||
model: 'model-a',
|
||||
maxOutputTokens: 1,
|
||||
plannedAtMs: 2_000,
|
||||
deadlineAtMs: 3_000,
|
||||
}),
|
||||
InvalidPluginPackagePromptExecutionPlanError,
|
||||
);
|
||||
assert.throws(() => prepare({ parameters: {} }), /subject is required/);
|
||||
assert.throws(
|
||||
() => prepare({ parameters: { subject: 'x', extra: 'y' } }),
|
||||
/undeclared name/,
|
||||
);
|
||||
assert.throws(
|
||||
() => prepare({ deadlineAtMs: 2_000 + 5 * 60_000 + 1 }),
|
||||
/deadline/,
|
||||
);
|
||||
assert.throws(() => prepare({ maxOutputTokens: 32_769 }), /maxOutputTokens/);
|
||||
});
|
||||
|
||||
test('creates one model StepRun admission without persisting Prompt content', () => {
|
||||
const prepared = prepare();
|
||||
const bundle = createPluginPackagePromptAdmissionBundle(prepared.plan);
|
||||
assert.equal(bundle.run.status, 'running');
|
||||
assert.equal(bundle.run.version, 2);
|
||||
assert.equal(bundle.run.eventSequence, 2);
|
||||
assert.equal(bundle.run.triggerType, 'plugin_package_prompt');
|
||||
assert.equal(bundle.stepMutation.stepRun.kind, 'model');
|
||||
assert.equal(bundle.stepMutation.stepRun.status, 'ready');
|
||||
assert.equal(bundle.stepMutation.expectedRunVersion, 1);
|
||||
assert.equal(bundle.stepMutation.event.sequence, 2);
|
||||
assert.equal(bundle.receipt.finalRunVersion, 2);
|
||||
assert.equal(bundle.receipt.invocationId, prepared.plan.invocationId);
|
||||
const durable = JSON.stringify(bundle);
|
||||
assert.equal(durable.includes('QingLong 3.0'), false);
|
||||
assert.equal(durable.includes('Summarize '), false);
|
||||
});
|
||||
|
||||
test('normalizer rejects identity and digest drift', () => {
|
||||
const plan = prepare().plan;
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackagePromptExecutionPlan({
|
||||
...plan,
|
||||
runId: 'ppr:drift',
|
||||
}),
|
||||
InvalidPluginPackagePromptExecutionPlanError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackagePromptExecutionPlan({
|
||||
...plan,
|
||||
parameterDigest: 'f'.repeat(64),
|
||||
}),
|
||||
InvalidPluginPackagePromptExecutionPlanError,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user