mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:17:47 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
|
||||
const {
|
||||
LOCAL_MODEL_INVOCATION_MIGRATION_PLAN_DIGEST,
|
||||
migrateLocalModelInvocationFeature,
|
||||
} = require('@qinglong/ai/model-invocation-migration');
|
||||
const {
|
||||
LocalModelInvocationFeatureActivationRepository,
|
||||
LocalModelInvocationFeatureTransitionConflictError,
|
||||
LocalModelInvocationFeatureTransitionUnavailableError,
|
||||
assertLocalModelInvocationFeatureActive,
|
||||
createLocalModelInvocationFeatureTransitionCommand,
|
||||
} = require('@qinglong/ai/local-feature-activation');
|
||||
|
||||
function createMainSqliteContract(client) {
|
||||
client.exec(`
|
||||
CREATE TABLE "QingLong3SchemaMigrations" (
|
||||
migration_id TEXT PRIMARY KEY,
|
||||
stream_id TEXT NOT NULL,
|
||||
dialect TEXT NOT NULL,
|
||||
checksum TEXT NOT NULL,
|
||||
applied_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE "Runs" (id TEXT PRIMARY KEY);
|
||||
CREATE TABLE "RunEvents" (id TEXT PRIMARY KEY);
|
||||
CREATE TABLE "StepRuns" (
|
||||
id TEXT PRIMARY KEY,
|
||||
run_id TEXT NOT NULL,
|
||||
UNIQUE (run_id, id)
|
||||
);
|
||||
CREATE TABLE "StepRunMutations" (mutation_id TEXT PRIMARY KEY);
|
||||
`);
|
||||
}
|
||||
|
||||
function principal(authenticationId = 'local_ai_feature:proof-1') {
|
||||
return {
|
||||
subject: { type: 'user', id: 'owner-user' },
|
||||
authenticationId,
|
||||
authenticatedAtMs: 1_000,
|
||||
expiresAtMs: 301_000,
|
||||
assurance: 'local_console',
|
||||
};
|
||||
}
|
||||
|
||||
function transition(overrides = {}) {
|
||||
return createLocalModelInvocationFeatureTransitionCommand({
|
||||
featureId: 'model-invocation',
|
||||
expectedGeneration: 0,
|
||||
expectedState: null,
|
||||
state: 'active',
|
||||
mutationId: 'feature-activation-1',
|
||||
requestId: 'feature-request-1',
|
||||
expectedMigrationDigest: LOCAL_MODEL_INVOCATION_MIGRATION_PLAN_DIGEST,
|
||||
safety: {
|
||||
mode: 'fresh_database',
|
||||
backupEvidenceDigest: null,
|
||||
},
|
||||
principal: principal(),
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
async function fixture() {
|
||||
const client = new DatabaseSync(':memory:');
|
||||
client.exec('PRAGMA foreign_keys = ON');
|
||||
createMainSqliteContract(client);
|
||||
await migrateLocalModelInvocationFeature(client);
|
||||
return client;
|
||||
}
|
||||
|
||||
test('local AI feature activation is append-only, replay-safe and non-destructive', async () => {
|
||||
const client = await fixture();
|
||||
const repository = new LocalModelInvocationFeatureActivationRepository(
|
||||
client,
|
||||
);
|
||||
|
||||
assert.equal(repository.findCurrent(), null);
|
||||
assert.throws(
|
||||
() => assertLocalModelInvocationFeatureActive(client),
|
||||
LocalModelInvocationFeatureTransitionUnavailableError,
|
||||
);
|
||||
|
||||
const activated = repository.transition(transition());
|
||||
assert.equal(activated.status, 'created');
|
||||
assert.equal(activated.transition.generation, 1);
|
||||
assert.equal(activated.transition.state, 'active');
|
||||
assert.equal(
|
||||
assertLocalModelInvocationFeatureActive(client).transitionDigest,
|
||||
activated.transition.transitionDigest,
|
||||
);
|
||||
|
||||
const replayed = repository.transition(transition());
|
||||
assert.equal(replayed.status, 'existing');
|
||||
assert.deepEqual(replayed.transition, activated.transition);
|
||||
|
||||
const deactivated = repository.transition(
|
||||
transition({
|
||||
expectedGeneration: 1,
|
||||
expectedState: 'active',
|
||||
state: 'inactive',
|
||||
mutationId: 'feature-deactivation-1',
|
||||
requestId: 'feature-request-2',
|
||||
safety: {
|
||||
mode: 'preserve_existing',
|
||||
backupEvidenceDigest: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
assert.equal(deactivated.status, 'created');
|
||||
assert.equal(deactivated.transition.generation, 2);
|
||||
assert.equal(deactivated.transition.state, 'inactive');
|
||||
assert.throws(
|
||||
() => assertLocalModelInvocationFeatureActive(client),
|
||||
LocalModelInvocationFeatureTransitionUnavailableError,
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
{
|
||||
...client
|
||||
.prepare(
|
||||
`SELECT
|
||||
(SELECT count(*) FROM "ModelInvocationFeatureTransitions") AS transitions,
|
||||
(SELECT count(*) FROM "ModelInvocationFeatureHead") AS heads,
|
||||
(SELECT count(*) FROM "ModelInvocationStarts") AS starts,
|
||||
(SELECT count(*) FROM "ModelPriceCatalogPublications") AS publications`,
|
||||
)
|
||||
.get(),
|
||||
},
|
||||
{ transitions: 2, heads: 1, starts: 0, publications: 0 },
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('local AI feature activation rejects CAS, plan and identity drift', async () => {
|
||||
const client = await fixture();
|
||||
const repository = new LocalModelInvocationFeatureActivationRepository(
|
||||
client,
|
||||
);
|
||||
repository.transition(transition());
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
repository.transition(
|
||||
transition({
|
||||
mutationId: 'feature-activation-conflict',
|
||||
requestId: 'feature-request-conflict',
|
||||
}),
|
||||
),
|
||||
LocalModelInvocationFeatureTransitionConflictError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
repository.transition(
|
||||
transition({
|
||||
expectedGeneration: 1,
|
||||
expectedState: 'active',
|
||||
state: 'inactive',
|
||||
mutationId: 'feature-deactivation-plan-drift',
|
||||
requestId: 'feature-request-plan-drift',
|
||||
expectedMigrationDigest: 'f'.repeat(64),
|
||||
safety: {
|
||||
mode: 'preserve_existing',
|
||||
backupEvidenceDigest: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
LocalModelInvocationFeatureTransitionConflictError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
repository.transition(
|
||||
transition({
|
||||
principal: principal('local_ai_feature:different-proof'),
|
||||
}),
|
||||
),
|
||||
LocalModelInvocationFeatureTransitionConflictError,
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('local AI feature transaction fence runs before replay and rolls back', async () => {
|
||||
const client = await fixture();
|
||||
let fences = 0;
|
||||
const first = new LocalModelInvocationFeatureActivationRepository(client, {
|
||||
beforeMutation() {
|
||||
fences += 1;
|
||||
},
|
||||
});
|
||||
first.transition(transition());
|
||||
first.transition(transition());
|
||||
assert.equal(fences, 2);
|
||||
|
||||
const rejected = new LocalModelInvocationFeatureActivationRepository(client, {
|
||||
beforeMutation() {
|
||||
throw new Error('fence rejected');
|
||||
},
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
rejected.transition(
|
||||
transition({
|
||||
expectedGeneration: 1,
|
||||
expectedState: 'active',
|
||||
state: 'inactive',
|
||||
mutationId: 'feature-deactivation-rejected',
|
||||
requestId: 'feature-request-rejected',
|
||||
safety: {
|
||||
mode: 'preserve_existing',
|
||||
backupEvidenceDigest: null,
|
||||
},
|
||||
}),
|
||||
),
|
||||
LocalModelInvocationFeatureTransitionUnavailableError,
|
||||
);
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM "ModelInvocationFeatureTransitions"`,
|
||||
)
|
||||
.get().count,
|
||||
1,
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
Reference in New Issue
Block a user