fix template, remove okr, use casan.*

This commit is contained in:
thanhnv
2026-07-18 16:45:31 +07:00
parent 0dfd1742d3
commit 13fae3e6c3
249 changed files with 4881 additions and 5702 deletions
@@ -16,6 +16,8 @@ export interface GoalProject {
domain: string;
domain_root: string;
context_roots: string[];
manifest?: string;
shell_root?: string;
}
export interface GoalProjectCreateInput {
@@ -109,6 +111,7 @@ const ORCHESTRATOR_CLI = join(HARNESS_BIN, 'goal-orchestrator.py');
const PATCH_EXECUTOR_CLI = join(HARNESS_BIN, 'goal-patch-executor.py');
const RBAC_CLI = join(HARNESS_BIN, 'rbac-check.py');
const PROJECT_REGISTRY = join(APP_ROOT, 'packages', 'casan-harness', 'level5', 'project-registry.json');
const PROJECT_SCAFFOLDER = join(APP_ROOT, 'packages', 'casan-devkit', 'project-scaffold.py');
function parseJson<T>(value: string): T | null {
try {
@@ -260,10 +263,10 @@ export class GoalsService {
if (actor.role !== 'org-admin') throw new ForbiddenException('GOAL_PROJECT_CREATE_DENIED');
const projectId = String(input.projectId ?? '').trim();
const domain = String(input.domain ?? '').trim();
if (!/^[A-Za-z][A-Za-z0-9._-]{2,63}$/.test(projectId)) {
if (!/^[a-z][a-z0-9-]{1,62}$/.test(projectId)) {
throw new BadRequestException('GOAL_PROJECT_ID_INVALID');
}
if (domain.length < 3 || domain.length > 100) {
if (!/^[\p{L}\p{N}][\p{L}\p{N} .&()'_-]{1,99}$/u.test(domain)) {
throw new BadRequestException('GOAL_PROJECT_DOMAIN_INVALID');
}
const lockPath = `${PROJECT_REGISTRY}.lock`;
@@ -291,13 +294,83 @@ export class GoalsService {
absoluteRoot = join(canonicalProjects, projectId);
mkdirSync(absoluteRoot, { mode: 0o750 });
createdRoot = true;
const relativeRoot = join('apps', 'projects', projectId);
const entry = { project_id: projectId, domain, domain_root: relativeRoot, context_roots: [relativeRoot], harness_package: 'fpt-casan-sdd-harness', harness_version: '1.0.0', status: 'active' };
execFileSync('python3', [PROJECT_SCAFFOLDER,
'--target', absoluteRoot,
'--project', projectId,
'--name', domain,
'--template', 'nestjs-react',
'--with-harness',
], {
cwd: APP_ROOT,
env: process.env,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 60_000,
});
const relativeRoot = join('apps', 'projects', projectId).replaceAll('\\', '/');
const embeddedAppRoot = `${relativeRoot}/apps/${projectId}`;
const adapterManifestPath = `${relativeRoot}/casan.workspace.manifest.json`;
const adapterManifest = {
schema_version: 1,
project_id: projectId,
display_name: domain,
domain_root: `${embeddedAppRoot}/domain`,
requirements: `${embeddedAppRoot}/domain/input/requirement.md`,
architecture: `${embeddedAppRoot}/domain/input/architecture.md`,
quality_profile: `${relativeRoot}/config/casan/quality-profiles/enterprise-web-v1.json`,
feature: { id: `001-${projectId}-app`, module_id: 'MOD-01', slug: `${projectId}-core`, title: `${domain} Core` },
source_roots: [`${embeddedAppRoot}/backend`, `${embeddedAppRoot}/frontend`],
commands: {
build: [['npm', '--prefix', relativeRoot, 'run', 'build']],
test: [['npm', '--prefix', relativeRoot, 'test']],
},
verification: [
{
path_prefix: `${embeddedAppRoot}/backend/`,
commands: [
['npm', '--prefix', relativeRoot, 'run', 'build', '-w', `@${projectId}/backend`],
['npm', '--prefix', relativeRoot, 'test', '-w', `@${projectId}/backend`],
],
},
{
path_prefix: `${embeddedAppRoot}/frontend/`,
commands: [
['npm', '--prefix', relativeRoot, 'run', 'build', '-w', `@${projectId}/frontend`],
['npm', '--prefix', relativeRoot, 'test', '-w', `@${projectId}/frontend`],
],
},
],
artifacts_root: `${relativeRoot}/docs/output`,
implementation_evidence: [
`${embeddedAppRoot}/backend/src/main.ts`,
`${embeddedAppRoot}/backend/test/health.test.ts`,
`${embeddedAppRoot}/frontend/src/App.tsx`,
`${embeddedAppRoot}/frontend/src/__tests__/App.test.tsx`,
],
tech_stack: 'NestJS 10, React 18, Vite 5, Tailwind CSS 3, strict TypeScript',
};
const adapterAbsolute = join(APP_ROOT, adapterManifestPath);
const adapterTemporary = `${adapterAbsolute}.${process.pid}.${randomUUID()}.tmp`;
writeFileSync(adapterTemporary, `${JSON.stringify(adapterManifest, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
renameSync(adapterTemporary, adapterAbsolute);
const entry = {
project_id: projectId,
domain,
domain_root: `${embeddedAppRoot}/domain`,
manifest: adapterManifestPath,
context_roots: [relativeRoot],
shell_root: relativeRoot,
harness_package: 'fpt-casan-sdd-harness',
harness_version: '1.0.0',
status: 'active',
};
const updated = { ...registry, projects: [...registry.projects, entry] };
const temporary = `${PROJECT_REGISTRY}.${process.pid}.${randomUUID()}.tmp`;
writeFileSync(temporary, `${JSON.stringify(updated, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
renameSync(temporary, PROJECT_REGISTRY);
return { project_id: projectId, domain, domain_root: relativeRoot, context_roots: [relativeRoot] };
return { project_id: projectId, domain, domain_root: entry.domain_root, manifest: adapterManifestPath, shell_root: relativeRoot, context_roots: [relativeRoot] };
} catch (error) {
if (createdRoot && absoluteRoot) rmSync(absoluteRoot, { recursive: true, force: true });
if (error instanceof HttpException) throw error;
@@ -379,7 +452,14 @@ export class GoalsService {
}
return relative;
});
return { project_id: projectId, domain: String(entry.domain ?? projectId), domain_root: domainRoot, context_roots: contextRoots };
return {
project_id: projectId,
domain: String(entry.domain ?? projectId),
domain_root: domainRoot,
context_roots: contextRoots,
manifest: entry.manifest ? String(entry.manifest) : undefined,
shell_root: entry.shell_root ? String(entry.shell_root) : undefined,
};
});
}
@@ -69,6 +69,39 @@ test('approval inbox submit -> approve applies governed setting and writes overs
});
});
test('operations owner request remains visible and actionable for an independent reviewer', async () => {
await withTempGovernance(async () => {
const svc = new ApprovalsService();
const submitted = svc.submit({
action: 'goal.workspace.execute',
target: 'customer-portal',
risk: 'high',
sensitive: true,
reason: 'apply the reviewed workspace patch',
payload: { goal_id: 'goal-123', project_id: 'customer-portal' },
}, projectAdmin) as { proposal: { id: string; status: string; proposer: string } };
const reviewerInbox = svc.list(approver, 'pending') as {
count: number;
proposals: Array<{ id: string; proposer: string; status: string }>;
};
const visible = reviewerInbox.proposals.find((proposal) => proposal.id === submitted.proposal.id);
assert.equal(reviewerInbox.count, 1);
assert.equal(visible?.proposer, projectAdmin.actor);
assert.equal(visible?.status, 'pending');
const decided = await svc.decide({
id: submitted.proposal.id,
decision: 'approve',
reason: 'independent reviewer verified scope and controls',
}, approver) as { proposal: { status: string; approver: string } };
assert.equal(decided.proposal.status, 'approved');
assert.equal(decided.proposal.approver, approver.actor);
});
});
test('approval inbox denies forged JWT in strict mode without deciding proposal', async () => {
await withTempGovernance(async ({ inbox }) => {
const svc = new ApprovalsService();
@@ -1,9 +1,14 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { GoalsService } from '../src/goals/goals.service.js';
const admin = { actor: 'goal-admin', role: 'org-admin', project: 'default', tenant: 'goal-test' };
const root = resolve(import.meta.dirname, '../../../..');
const registry = resolve(root, 'packages/casan-harness/level5/project-registry.json');
test('goal project selector exposes only active allowlisted registry entries', () => {
const result = new GoalsService().projects(admin);
@@ -25,3 +30,37 @@ test('goal project creation is restricted to organization administrators', () =>
ForbiddenException,
);
});
test('goal project creation rejects unsafe template values before touching the workspace', () => {
assert.throws(
() => new GoalsService().createProject({ projectId: 'unsafe-shell', domain: 'Broken "Template"' }, admin),
BadRequestException,
);
});
test('goal project creation builds a complete isolated shell and central adapter manifest', () => {
const projectId = `goal-shell-${process.pid}`;
const projectRoot = resolve(root, 'apps/projects', projectId);
const registryBefore = readFileSync(registry, 'utf8');
try {
const created = new GoalsService().createProject({ projectId, domain: 'Goal Shell Verification' }, admin);
assert.equal(created.project_id, projectId);
assert.equal(created.shell_root, `apps/projects/${projectId}`);
assert.equal(created.manifest, `apps/projects/${projectId}/casan.workspace.manifest.json`);
assert.ok(existsSync(resolve(projectRoot, '.github/workflows/ci.yml')));
assert.ok(existsSync(resolve(projectRoot, `apps/${projectId}/domain/project.manifest.json`)));
assert.ok(existsSync(resolve(projectRoot, 'packages/casan-harness/scripts/bash/project-gate.sh')));
assert.ok(existsSync(resolve(projectRoot, 'casan.workspace.manifest.json')));
const validation = execFileSync('python3', [
resolve(root, 'packages/casan-harness/scripts/bash/project_manifest.py'),
'validate', '--root', root, '--manifest', created.manifest!,
], { encoding: 'utf8' });
assert.match(validation, /"status": "valid"/);
assert.ok(new GoalsService().projects(admin).projects.some((project) => project.project_id === projectId && project.shell_root === created.shell_root));
} finally {
writeFileSync(registry, registryBefore, 'utf8');
rmSync(projectRoot, { recursive: true, force: true });
rmSync(`${registry}.lock`, { force: true });
}
});