feat: add memindadm runtime policy for agent code runs (Phase 1.5)
Persist code-run gates in admin DB and expose them via /auth/status so H5 can honor runtime policy without VITE rebuilds. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
agentCodeRunAdminConfigInternals,
|
||||
createAgentCodeRunAdminConfigService,
|
||||
isTaskTypeAllowedByCodeRunPolicy,
|
||||
isUserAllowedByCodeRunPolicy,
|
||||
} from './agent-code-run-admin-config.mjs';
|
||||
|
||||
function createMemoryPool(initialRow = null) {
|
||||
const state = { row: initialRow };
|
||||
return {
|
||||
state,
|
||||
async query(sql, params = []) {
|
||||
const normalized = String(sql).replace(/\s+/g, ' ').trim();
|
||||
if (normalized.startsWith('CREATE TABLE')) return [[]];
|
||||
if (normalized.includes('SELECT config_json')) {
|
||||
return [state.row ? [state.row] : []];
|
||||
}
|
||||
if (normalized.includes('INSERT INTO h5_agent_code_run_config')) {
|
||||
state.row = {
|
||||
config_json: params[1],
|
||||
updated_by: params[2],
|
||||
updated_at: params[3],
|
||||
};
|
||||
return [{ affectedRows: 1 }];
|
||||
}
|
||||
return [[]];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('default policy is fail closed', async () => {
|
||||
const service = createAgentCodeRunAdminConfigService(createMemoryPool(), { env: {} });
|
||||
const policy = await service.getEffectivePolicy('user-1');
|
||||
assert.equal(policy.enabled, false);
|
||||
assert.equal(policy.clientEnabled, false);
|
||||
assert.equal(policy.pageDataDevAutodetect, false);
|
||||
assert.equal(policy.source, 'default');
|
||||
});
|
||||
|
||||
test('env migration activates when legacy env flags are set', async () => {
|
||||
const service = createAgentCodeRunAdminConfigService(createMemoryPool(), {
|
||||
env: {
|
||||
MEMIND_AGENT_CODE_RUNS_ENABLED: '1',
|
||||
VITE_AGENT_PAGE_DATA_DEV_AUTODETECT: '1',
|
||||
MEMIND_AGENT_CODE_RUNS_USER_IDS: 'user-a',
|
||||
},
|
||||
});
|
||||
const policy = await service.getEffectivePolicy('user-a');
|
||||
assert.equal(policy.source, 'env-migration');
|
||||
assert.equal(policy.enabled, true);
|
||||
assert.equal(policy.pageDataDevAutodetect, true);
|
||||
assert.equal(policy.userAllowed, true);
|
||||
assert.equal(isUserAllowedByCodeRunPolicy('user-b', policy), false);
|
||||
});
|
||||
|
||||
test('admin-db config overrides env migration defaults', async () => {
|
||||
const pool = createMemoryPool({
|
||||
config_json: JSON.stringify({
|
||||
codeRun: {
|
||||
enabled: true,
|
||||
clientEnabled: true,
|
||||
pageDataDevAutodetect: false,
|
||||
generalAutodetect: false,
|
||||
requireValidation: true,
|
||||
userAllowlist: [],
|
||||
taskTypeAllowlist: ['page_data_dev'],
|
||||
},
|
||||
pageDataDev: { autodetect: true },
|
||||
}),
|
||||
updated_by: 'admin-1',
|
||||
updated_at: 123,
|
||||
});
|
||||
const service = createAgentCodeRunAdminConfigService(pool, {
|
||||
env: { MEMIND_AGENT_CODE_RUNS_ENABLED: '0' },
|
||||
});
|
||||
const policy = await service.getEffectivePolicy('user-1');
|
||||
assert.equal(policy.source, 'admin-db');
|
||||
assert.equal(policy.enabled, true);
|
||||
assert.equal(policy.pageDataDevAutodetect, true);
|
||||
assert.equal(isTaskTypeAllowedByCodeRunPolicy('page_data_dev', policy), true);
|
||||
assert.equal(isTaskTypeAllowedByCodeRunPolicy('h5_chat_code_task', policy), false);
|
||||
});
|
||||
|
||||
test('updateAdminConfig persists patch to database', async () => {
|
||||
const pool = createMemoryPool();
|
||||
const service = createAgentCodeRunAdminConfigService(pool, { env: {} });
|
||||
const saved = await service.updateAdminConfig(
|
||||
{
|
||||
config: {
|
||||
codeRun: { enabled: true, clientEnabled: true },
|
||||
pageDataDev: { autodetect: true },
|
||||
},
|
||||
},
|
||||
{ updatedBy: 'admin-1' },
|
||||
);
|
||||
assert.equal(saved.source, 'admin-db');
|
||||
assert.equal(saved.config.codeRun.enabled, true);
|
||||
assert.equal(saved.config.pageDataDev.autodetect, true);
|
||||
const clientPolicy = await service.getPublicClientPolicy('user-1');
|
||||
assert.equal(clientPolicy.codeRun.enabled, true);
|
||||
assert.equal(clientPolicy.codeRun.pageDataDevAutodetect, true);
|
||||
});
|
||||
|
||||
test('MEMIND_CODE_RUN_POLICY_SOURCE=env locks admin updates', async () => {
|
||||
const service = createAgentCodeRunAdminConfigService(createMemoryPool(), {
|
||||
env: { MEMIND_CODE_RUN_POLICY_SOURCE: 'env', MEMIND_AGENT_CODE_RUNS_ENABLED: '1' },
|
||||
});
|
||||
await assert.rejects(
|
||||
() => service.updateAdminConfig({ config: { codeRun: { enabled: false } } }),
|
||||
/不允许通过后台修改/,
|
||||
);
|
||||
});
|
||||
|
||||
test('getPublicClientPolicy disables client flags when user not allowlisted', async () => {
|
||||
const config = agentCodeRunAdminConfigInternals.defaultConfigShape();
|
||||
config.codeRun.enabled = true;
|
||||
config.codeRun.clientEnabled = true;
|
||||
config.codeRun.userAllowlist = ['allowed-user'];
|
||||
config.pageDataDev.autodetect = true;
|
||||
const pool = createMemoryPool({
|
||||
config_json: JSON.stringify(config),
|
||||
updated_by: null,
|
||||
updated_at: 1,
|
||||
});
|
||||
const service = createAgentCodeRunAdminConfigService(pool, { env: {} });
|
||||
const denied = await service.getPublicClientPolicy('other-user');
|
||||
assert.equal(denied.codeRun.enabled, false);
|
||||
const allowed = await service.getPublicClientPolicy('allowed-user');
|
||||
assert.equal(allowed.codeRun.enabled, true);
|
||||
assert.equal(allowed.codeRun.pageDataDevAutodetect, true);
|
||||
});
|
||||
Reference in New Issue
Block a user