feat: updade workspace
This commit is contained in:
@@ -16,8 +16,10 @@ function withTempGovernance(fn: (paths: { inbox: string; store: string }) => voi
|
||||
const prevStore = process.env.CASAN_CP_STORE_FILE;
|
||||
const prevKeyDir = process.env.CASAN_CP_KEY_DIR;
|
||||
const prevPub = process.env.CASAN_CP_PUB;
|
||||
const prevTenantRoot = process.env.CASAN_TENANT_STATE_ROOT;
|
||||
const work = mkdtempSync(join(tmpdir(), 'cp-approval-'));
|
||||
process.env.CASAN_APPROVAL_INBOX_FILE = join(work, 'approval-inbox.json');
|
||||
process.env.CASAN_TENANT_STATE_ROOT = work;
|
||||
process.env.CASAN_APPROVAL_INBOX_FILE = join(work, 'default', 'approvals', 'approval-inbox.json');
|
||||
process.env.CASAN_CP_STORE_FILE = join(work, 'settings.json');
|
||||
process.env.CASAN_CP_KEY_DIR = join(work, 'keys');
|
||||
process.env.CASAN_CP_PUB = join(work, 'cp.pub');
|
||||
@@ -34,6 +36,8 @@ function withTempGovernance(fn: (paths: { inbox: string; store: string }) => voi
|
||||
else process.env.CASAN_CP_KEY_DIR = prevKeyDir;
|
||||
if (prevPub === undefined) delete process.env.CASAN_CP_PUB;
|
||||
else process.env.CASAN_CP_PUB = prevPub;
|
||||
if (prevTenantRoot === undefined) delete process.env.CASAN_TENANT_STATE_ROOT;
|
||||
else process.env.CASAN_TENANT_STATE_ROOT = prevTenantRoot;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { GoalsService } from '../src/goals/goals.service.js';
|
||||
|
||||
const root = join(import.meta.dirname, '..', '..', '..', '..');
|
||||
const admin = { actor: 'goal-admin', role: 'org-admin', project: 'default', tenant: 'goal-test' };
|
||||
|
||||
test('goal project selector exposes only active allowlisted registry entries', () => {
|
||||
const result = new GoalsService().projects(admin);
|
||||
assert.ok(result.projects.some((project) => project.project_id === 'AINative_OKR_CASAN4'));
|
||||
assert.ok(result.projects.every((project) => project.context_roots.every((contextRoot) => !contextRoot.startsWith('/'))));
|
||||
assert.ok(result.projects.every((project) => project.project_id !== 'CASAN_DEMO_PROJECT_A'));
|
||||
});
|
||||
|
||||
test('goal start rejects project ids outside the server registry before model routing', async () => {
|
||||
await assert.rejects(
|
||||
new GoalsService().start({ goal: 'Review the current application architecture safely', projectId: '../../tmp' }, admin),
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
|
||||
test('H1 creates a bounded manifest and routes workspace side effects to approval without writing source', () => {
|
||||
const stateRoot = mkdtempSync(join(tmpdir(), 'casan-goal-context-'));
|
||||
const tenantRoot = join(stateRoot, 'tenants');
|
||||
const jobDirectory = join(stateRoot, 'state', 'goals', 'goal-test');
|
||||
mkdirSync(jobDirectory, { recursive: true });
|
||||
const jobPath = join(jobDirectory, '11111111-1111-1111-1111-111111111111.json');
|
||||
writeFileSync(jobPath, JSON.stringify({
|
||||
id: '11111111-1111-1111-1111-111111111111',
|
||||
trace_id: '11111111-1111-1111-1111-111111111111',
|
||||
goal: 'Hãy sửa code OKR để thêm một nút mới ngay bây giờ',
|
||||
status: 'queued', actor: 'goal-admin', tenant: 'goal-test', project: 'AINative_OKR_CASAN4',
|
||||
created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
|
||||
local_provider: 'local-policy', local_model: 'unused', cloud_provider: 'unused', cloud_model: 'unused',
|
||||
stages: [
|
||||
{ id: 'local-worker', status: 'queued', detail: 'Waiting', provider: '', model: '' },
|
||||
{ id: 'cloud-reviewer', status: 'queued', detail: 'Waiting', provider: '', model: '' },
|
||||
],
|
||||
}));
|
||||
execFileSync('python3', [join(root, 'packages/casan-harness/scripts/bash/goal-orchestrator.py'), '--job-file', jobPath], {
|
||||
cwd: root,
|
||||
env: { ...process.env, CASAN_STATE_ROOT: stateRoot, CASAN_TENANT_ID: 'goal-test', CASAN_TENANT_STATE_ROOT: tenantRoot },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout: 60_000,
|
||||
});
|
||||
const job = JSON.parse(readFileSync(jobPath, 'utf8')) as { status: string; context_manifest: { files: number; characters: number }; approval: { action: string } };
|
||||
assert.equal(job.status, 'requires_approval');
|
||||
assert.equal(job.approval.action, 'goal.workspace.execute');
|
||||
assert.ok(job.context_manifest.files > 0);
|
||||
assert.ok(job.context_manifest.files <= 16);
|
||||
assert.ok(job.context_manifest.characters <= 7000);
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||
import { SettingsService } from '../src/settings/settings.service.js';
|
||||
|
||||
const viewer = { actor: 'viewer-1', role: 'viewer', project: 'default', tenant: 'default' };
|
||||
@@ -41,6 +41,17 @@ test('viewer cannot write settings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('invalid setting value is rejected before it reaches the store', () => {
|
||||
withTempStore((storeFile) => {
|
||||
const svc = new SettingsService();
|
||||
assert.throws(
|
||||
() => svc.set({ key: 'loop.max_steps', value: 0, reason: 'invalid lower bound' }, admin),
|
||||
BadRequestException,
|
||||
);
|
||||
assert.equal(existsSync(storeFile), false);
|
||||
});
|
||||
});
|
||||
|
||||
test('org-admin writes and rolls back through governed store with audit', () => {
|
||||
withTempStore((storeFile) => {
|
||||
const svc = new SettingsService();
|
||||
|
||||
Reference in New Issue
Block a user