mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 10:32:40 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const { DatabaseSync } = require('node:sqlite');
|
||||
|
||||
const {
|
||||
MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA,
|
||||
ModelProviderCredentialTransitionConflictError,
|
||||
createModelProviderCredentialTransitionCommand,
|
||||
} = require('../dist/model-provider-credential/modelProviderCredentialCatalog.js');
|
||||
const {
|
||||
ModelProviderCredentialAdministrationAuthorizationFenceConflictError,
|
||||
modelProviderCredentialAdministrationOperationId,
|
||||
} = require('../dist/model-provider-credential/modelProviderCredentialAdministration.js');
|
||||
const {
|
||||
LocalModelProviderCredentialRepository,
|
||||
} = require('../dist/model-provider-credential/localModelProviderCredentialRepository.js');
|
||||
const {
|
||||
LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE,
|
||||
migrateLocalModelInvocationFeature,
|
||||
} = require('@qinglong/ai/model-invocation-migration');
|
||||
const {
|
||||
MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
|
||||
MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA,
|
||||
digestModelProviderCredentialBinding,
|
||||
} = require('../dist/model-provider-credential/providerCredential.js');
|
||||
const { createSecretRef } = require('@qinglong/runtime-core/secret-reference');
|
||||
|
||||
const PROJECT_ID = 'project-a';
|
||||
const PROVIDER = 'openai-compatible';
|
||||
const ACTOR = Object.freeze({ type: 'user', id: 'owner-a' });
|
||||
const FENCE = Object.freeze({ projectVersion: 3, bindingVersion: 7 });
|
||||
const BIND_MUTATION_ID = '019f7094-a853-4f3b-82ab-dfa08e6bd1c1';
|
||||
|
||||
function createMainContract(client) {
|
||||
client.exec(`
|
||||
PRAGMA foreign_keys = ON;
|
||||
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);
|
||||
CREATE TABLE "QingLong3LocalSecretEnvelopes" (
|
||||
project_id TEXT NOT NULL,
|
||||
secret_name TEXT NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
PRIMARY KEY (project_id, secret_name, version)
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
async function fixture(options = {}) {
|
||||
const client = new DatabaseSync(':memory:');
|
||||
createMainContract(client);
|
||||
await migrateLocalModelInvocationFeature(client);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO "QingLong3LocalSecretEnvelopes"
|
||||
(project_id, secret_name, version) VALUES (?, ?, ?)`,
|
||||
)
|
||||
.run(PROJECT_ID, 'openai-token', 1);
|
||||
return {
|
||||
client,
|
||||
repository: new LocalModelProviderCredentialRepository(client, {
|
||||
now: options.now ?? (() => 100),
|
||||
...(options.authorization
|
||||
? { authorization: options.authorization }
|
||||
: {}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function binding(overrides = {}) {
|
||||
return Object.freeze({
|
||||
schema: MODEL_PROVIDER_CREDENTIAL_BINDING_SCHEMA,
|
||||
projectId: PROJECT_ID,
|
||||
provider: PROVIDER,
|
||||
revision: 'credential-v1',
|
||||
secretRef: createSecretRef({
|
||||
projectId: PROJECT_ID,
|
||||
name: 'openai-token',
|
||||
version: 1,
|
||||
}),
|
||||
scheme: 'bearer',
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function command(overrides = {}) {
|
||||
return createModelProviderCredentialTransitionCommand({
|
||||
schema: MODEL_PROVIDER_CREDENTIAL_TRANSITION_COMMAND_SCHEMA,
|
||||
mutationId: BIND_MUTATION_ID,
|
||||
projectId: PROJECT_ID,
|
||||
provider: PROVIDER,
|
||||
expectedGeneration: 0,
|
||||
action: 'bind',
|
||||
binding: binding(),
|
||||
changedBy: ACTOR,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function allowedAudit(catalogCommand, overrides = {}) {
|
||||
return Object.freeze({
|
||||
eventId: catalogCommand.mutationId,
|
||||
requestId: 'request-administration-1',
|
||||
operationId: modelProviderCredentialAdministrationOperationId(
|
||||
catalogCommand.action,
|
||||
),
|
||||
projectId: catalogCommand.projectId,
|
||||
subject: ACTOR,
|
||||
authenticationId: 'authentication-1',
|
||||
outcome: 'allowed',
|
||||
reasons: ['project_owner'],
|
||||
fence: FENCE,
|
||||
occurredAtMs: 99,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function authorized(catalogCommand) {
|
||||
return Object.freeze({
|
||||
command: catalogCommand,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: allowedAudit(catalogCommand),
|
||||
});
|
||||
}
|
||||
|
||||
function useAudit(activeBinding, requestId, occurredAtMs) {
|
||||
return Object.freeze({
|
||||
schema: MODEL_PROVIDER_CREDENTIAL_AUDIT_SCHEMA,
|
||||
operation: 'generate',
|
||||
projectId: PROJECT_ID,
|
||||
provider: PROVIDER,
|
||||
requestId,
|
||||
bindingRevision: activeBinding.revision,
|
||||
bindingDigest: digestModelProviderCredentialBinding(activeBinding),
|
||||
occurredAtMs,
|
||||
});
|
||||
}
|
||||
|
||||
test('local credential repository binds an existing SecretRef and replays exactly', async () => {
|
||||
const { client, repository } = await fixture();
|
||||
const bind = command();
|
||||
|
||||
const created = await repository.commit(bind);
|
||||
const replay = await repository.commit(bind);
|
||||
assert.equal(created.status, 'created');
|
||||
assert.equal(replay.status, 'existing');
|
||||
assert.deepEqual(replay.transition, created.transition);
|
||||
assert.deepEqual(
|
||||
await repository.findCurrentTransition(PROJECT_ID, PROVIDER),
|
||||
created.transition,
|
||||
);
|
||||
assert.deepEqual(
|
||||
await repository.resolveModelProviderCredentialBinding({
|
||||
projectId: PROJECT_ID,
|
||||
provider: PROVIDER,
|
||||
}),
|
||||
bind.binding,
|
||||
);
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM "${LOCAL_MODEL_INVOCATION_MIGRATION_HISTORY_TABLE}"`,
|
||||
)
|
||||
.get().count,
|
||||
13,
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('local credential repository fails closed for missing SecretRef and stale generation', async () => {
|
||||
const { client, repository } = await fixture();
|
||||
const missing = command({
|
||||
mutationId: '019f7094-a853-4f3b-82ab-dfa08e6bd1c2',
|
||||
binding: binding({
|
||||
secretRef: createSecretRef({
|
||||
projectId: PROJECT_ID,
|
||||
name: 'missing-token',
|
||||
version: 1,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
await assert.rejects(
|
||||
repository.commit(missing),
|
||||
ModelProviderCredentialTransitionConflictError,
|
||||
);
|
||||
assert.equal(
|
||||
await repository.findCurrentTransition(PROJECT_ID, PROVIDER),
|
||||
null,
|
||||
);
|
||||
|
||||
await repository.commit(command());
|
||||
await assert.rejects(
|
||||
repository.commit(
|
||||
command({
|
||||
mutationId: '019f7094-a853-4f3b-82ab-dfa08e6bd1c3',
|
||||
}),
|
||||
),
|
||||
ModelProviderCredentialTransitionConflictError,
|
||||
);
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM "ModelInvocationProviderCredentialTransitions"`,
|
||||
)
|
||||
.get().count,
|
||||
1,
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('authorized mutation and inspection revalidate inside the repository transaction', async () => {
|
||||
const confirmations = [];
|
||||
const authorization = {
|
||||
confirm(input) {
|
||||
assert.equal(input.value.actor.id, ACTOR.id);
|
||||
assert.equal(input.value.fence.projectVersion, FENCE.projectVersion);
|
||||
confirmations.push({ kind: input.kind, replay: input.replay });
|
||||
},
|
||||
};
|
||||
const { client, repository } = await fixture({ authorization });
|
||||
const bind = command();
|
||||
|
||||
assert.equal(
|
||||
(await repository.commitAuthorized(authorized(bind))).status,
|
||||
'created',
|
||||
);
|
||||
assert.equal(
|
||||
(await repository.commitAuthorized(authorized(bind))).status,
|
||||
'existing',
|
||||
);
|
||||
const inspected = await repository.inspectAuthorized({
|
||||
projectId: PROJECT_ID,
|
||||
provider: PROVIDER,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: {
|
||||
...allowedAudit(bind),
|
||||
eventId: '019f7094-a853-4f3b-82ab-dfa08e6bd1c4',
|
||||
operationId: 'model_provider_credential.inspect',
|
||||
},
|
||||
});
|
||||
assert.equal(inspected.generation, 1);
|
||||
assert.deepEqual(confirmations, [
|
||||
{ kind: 'mutation', replay: false },
|
||||
{ kind: 'mutation', replay: true },
|
||||
{ kind: 'inspection', replay: false },
|
||||
]);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('authorized replay fails closed when no authorization guard is installed', async () => {
|
||||
const { client, repository } = await fixture();
|
||||
const bind = command();
|
||||
await repository.commit(bind);
|
||||
await assert.rejects(
|
||||
repository.commitAuthorized(authorized(bind)),
|
||||
ModelProviderCredentialAdministrationAuthorizationFenceConflictError,
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('authorization fence rejection rolls the complete credential mutation back', async () => {
|
||||
const authorization = {
|
||||
confirm() {
|
||||
throw new ModelProviderCredentialAdministrationAuthorizationFenceConflictError();
|
||||
},
|
||||
};
|
||||
const { client, repository } = await fixture({ authorization });
|
||||
await assert.rejects(
|
||||
repository.commitAuthorized(authorized(command())),
|
||||
ModelProviderCredentialAdministrationAuthorizationFenceConflictError,
|
||||
);
|
||||
assert.deepEqual(
|
||||
{
|
||||
...client
|
||||
.prepare(
|
||||
`SELECT
|
||||
(SELECT count(*) FROM "ModelInvocationProviderCredentialBindings") AS bindings,
|
||||
(SELECT count(*) FROM "ModelInvocationProviderCredentialTransitions") AS transitions`,
|
||||
)
|
||||
.get(),
|
||||
},
|
||||
{ bindings: 0, transitions: 0 },
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
|
||||
test('credential use audit is content-free, idempotent and invalid after revoke', async () => {
|
||||
const { client, repository } = await fixture();
|
||||
const bind = command();
|
||||
await repository.commit(bind);
|
||||
|
||||
await repository.record(useAudit(bind.binding, 'provider-request-1', 110));
|
||||
await repository.record(useAudit(bind.binding, 'provider-request-1', 999));
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT count(*) AS count
|
||||
FROM "ModelInvocationProviderCredentialAudits"`,
|
||||
)
|
||||
.get().count,
|
||||
1,
|
||||
);
|
||||
assert.equal(
|
||||
client
|
||||
.prepare(
|
||||
`SELECT audit_json AS value
|
||||
FROM "ModelInvocationProviderCredentialAudits"`,
|
||||
)
|
||||
.get()
|
||||
.value.includes('secretRef'),
|
||||
false,
|
||||
);
|
||||
|
||||
const revoke = command({
|
||||
mutationId: '019f7094-a853-4f3b-82ab-dfa08e6bd1c5',
|
||||
expectedGeneration: 1,
|
||||
action: 'revoke',
|
||||
binding: null,
|
||||
});
|
||||
await repository.commit(revoke);
|
||||
assert.equal(
|
||||
await repository.resolveModelProviderCredentialBinding({
|
||||
projectId: PROJECT_ID,
|
||||
provider: PROVIDER,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
await assert.rejects(
|
||||
repository.record(useAudit(bind.binding, 'provider-request-2', 120)),
|
||||
ModelProviderCredentialTransitionConflictError,
|
||||
);
|
||||
client.close();
|
||||
});
|
||||
Reference in New Issue
Block a user