계획 계약 명세서 정의 및 검증 기준 분석 #1
1 changed files with 300 additions and 0 deletions
300
planning-contract/contract-validator.ts
Normal file
300
planning-contract/contract-validator.ts
Normal file
|
|
@ -0,0 +1,300 @@
|
||||||
|
/**
|
||||||
|
* 계획 계약 검증기 (Planning Contract Validator)
|
||||||
|
*
|
||||||
|
* 조항별 검증 로직을 구현하여 계획의 계약 충족 여부를 판단한다.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Plan, ClauseResult, ContractGrade, Epitaph } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 1: 목표 명확성 검증
|
||||||
|
*/
|
||||||
|
export function validateGoalClarity(plan: Plan): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
// 목표가 존재하는지 확인
|
||||||
|
if (!plan.goal) {
|
||||||
|
issues.push('목표가 정의되지 않음');
|
||||||
|
return { passed: false, issues };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 목표가 구체적인지 확인 (모호한 표현 체크)
|
||||||
|
const vaguePatterns = [
|
||||||
|
/좋게|잘|빠르게|최적으로|최대한/g,
|
||||||
|
/better|good|faster|optimal|best/g
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pattern of vaguePatterns) {
|
||||||
|
if (pattern.test(plan.goal.description)) {
|
||||||
|
issues.push(`모호한 표현 감지: "${plan.goal.description.match(pattern)?.[0]}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 측정 가능한 기준 존재 확인
|
||||||
|
if (!plan.goal.criteria || plan.goal.criteria.length === 0) {
|
||||||
|
issues.push('목표 달성 판단 기준이 없음');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단일 책임 원칙 확인
|
||||||
|
if (plan.goal.subGoals && plan.goal.subGoals.length > 2) {
|
||||||
|
issues.push('두 개 이상의 독립적 목표 포함 (단일 책임 위반)');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 2: 단계 완전성 검증
|
||||||
|
*/
|
||||||
|
export function validateStepCompleteness(plan: Plan): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
if (!plan.steps || plan.steps.length === 0) {
|
||||||
|
issues.push('단계가 정의되지 않음');
|
||||||
|
return { passed: false, issues };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 각 단계의 선행 조건 확인
|
||||||
|
for (let i = 0; i < plan.steps.length; i++) {
|
||||||
|
const step = plan.steps[i];
|
||||||
|
|
||||||
|
if (step.dependencies) {
|
||||||
|
for (const depId of step.dependencies) {
|
||||||
|
const depIndex = plan.steps.findIndex(s => s.id === depId);
|
||||||
|
if (depIndex === -1) {
|
||||||
|
issues.push(`단계 ${step.id}: 존재하지 않는 의존성 ${depId}`);
|
||||||
|
} else if (depIndex >= i) {
|
||||||
|
issues.push(`단계 ${step.id}: 의존성 ${depId}가 순서 위반 (${depIndex} >= ${i})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최종 단계에서 목표 달성 확인
|
||||||
|
const lastStep = plan.steps[plan.steps.length - 1];
|
||||||
|
if (lastStep && plan.goal) {
|
||||||
|
if (!lastStep.produces?.some(p => plan.goal?.produces?.includes(p))) {
|
||||||
|
// 목표 달성을 위한产出가 최종 단계에 없음
|
||||||
|
const hasGoalConnection = plan.steps.some(step =>
|
||||||
|
step.produces?.some(p => plan.goal?.produces?.includes(p))
|
||||||
|
);
|
||||||
|
if (!hasGoalConnection) {
|
||||||
|
issues.push('어떤 단계도 목표 달성에 기여하지 않음');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 3: 실행 가능성 검증
|
||||||
|
*/
|
||||||
|
export function validateExecutability(plan: Plan, availableCommands: string[]): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
if (!plan.steps) return { passed: false, issues: ['단계 없음'] };
|
||||||
|
|
||||||
|
for (const step of plan.steps) {
|
||||||
|
// 명령어 존재 확인
|
||||||
|
if (step.command && !availableCommands.includes(step.command)) {
|
||||||
|
issues.push(`단계 ${step.id}: 명령어 "${step.command}" 사용 불가`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 리소스 확인
|
||||||
|
if (step.requiredResources) {
|
||||||
|
for (const resource of step.requiredResources) {
|
||||||
|
if (!availableCommands.includes(resource)) {
|
||||||
|
issues.push(`단계 ${step.id}: 필요한 리소스 "${resource}" 없음`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 타임아웃 확인
|
||||||
|
if (!step.timeout || step.timeout <= 0) {
|
||||||
|
issues.push(`단계 ${step.id}: 타임아웃 미설정`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 4: 리스크 평가 검증
|
||||||
|
*/
|
||||||
|
export function validateRiskAssessment(plan: Plan): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
if (!plan.risks || plan.risks.length === 0) {
|
||||||
|
issues.push('리스크 식별 없음');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.risks) {
|
||||||
|
for (const risk of plan.risks) {
|
||||||
|
// 리스크에 대한 완화 전략 확인
|
||||||
|
if (!risk.mitigation) {
|
||||||
|
issues.push(`리스크 "${risk.description}": 완화 전략 없음`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대안 경로 확인 (중간 리스크 이상)
|
||||||
|
if (risk.severity === 'high' || risk.severity === 'critical') {
|
||||||
|
if (!risk.alternativePath) {
|
||||||
|
issues.push(`고위험 리스크 "${risk.description}": 대안 경로 없음`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 5: 검증 가능성 검증
|
||||||
|
*/
|
||||||
|
export function validateVerifiability(plan: Plan): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
if (!plan.steps) return { passed: false, issues: ['단계 없음'] };
|
||||||
|
|
||||||
|
for (const step of plan.steps) {
|
||||||
|
// 예상 결과 확인
|
||||||
|
if (!step.expectedOutcome) {
|
||||||
|
issues.push(`단계 ${step.id}: 예상 결과 미정의`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 어설션 확인
|
||||||
|
if (!step.assertions || step.assertions.length === 0) {
|
||||||
|
issues.push(`단계 ${step.id}: 검증 어설션 없음`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 6: 시간적 제약 검증
|
||||||
|
*/
|
||||||
|
export function validateTemporalConstraints(plan: Plan): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
// 전체 예상 소요 시간 확인
|
||||||
|
if (!plan.estimatedDuration) {
|
||||||
|
issues.push('전체 예상 소요 시간 미정의');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 각 단계 타임아웃 확인
|
||||||
|
if (plan.steps) {
|
||||||
|
for (const step of plan.steps) {
|
||||||
|
if (!step.timeout || step.timeout <= 0) {
|
||||||
|
issues.push(`단계 ${step.id}: 타임아웃 없음`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시간 초과 시 대체 전략 확인
|
||||||
|
if (plan.timeoutStrategy) {
|
||||||
|
issues.push('시간 초과 시 대체 전략 없음');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조항 7: 롤백 가능성 검증
|
||||||
|
*/
|
||||||
|
export function validateRollbackCapability(plan: Plan): ClauseResult {
|
||||||
|
const issues: string[] = [];
|
||||||
|
|
||||||
|
// 롤백 명령 정의 확인
|
||||||
|
if (!plan.rollbackCommands || plan.rollbackCommands.length === 0) {
|
||||||
|
issues.push('롤백 명령 미정의');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 롤백 시퀀스 순서 확인
|
||||||
|
if (plan.rollbackCommands) {
|
||||||
|
for (let i = 0; i < plan.rollbackCommands.length; i++) {
|
||||||
|
const cmd = plan.rollbackCommands[i];
|
||||||
|
if (!cmd.order || cmd.order !== plan.rollbackCommands.length - i) {
|
||||||
|
issues.push(`롤백 명령 ${cmd.id}: 순서 미지정 또는 역순 아님`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 롤백 성공 여부 검증 방법 확인
|
||||||
|
if (plan.rollbackCommands) {
|
||||||
|
for (const cmd of plan.rollbackCommands) {
|
||||||
|
if (!cmd.verification) {
|
||||||
|
issues.push(`롤백 명령 ${cmd.id}: 성공 여부 검증 방법 없음`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
passed: issues.length === 0,
|
||||||
|
issues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 전체 계약 검증
|
||||||
|
*/
|
||||||
|
export function validateContract(
|
||||||
|
plan: Plan,
|
||||||
|
availableCommands: string[] = []
|
||||||
|
): { grade: ContractGrade; results: Record<string, ClauseResult> } {
|
||||||
|
const results: Record<string, ClauseResult> = {
|
||||||
|
goalClarity: validateGoalClarity(plan),
|
||||||
|
stepCompleteness: validateStepCompleteness(plan),
|
||||||
|
executability: validateExecutability(plan, availableCommands),
|
||||||
|
riskAssessment: validateRiskAssessment(plan),
|
||||||
|
verifiability: validateVerifiability(plan),
|
||||||
|
temporalConstraints: validateTemporalConstraints(plan),
|
||||||
|
rollbackCapability: validateRollbackCapability(plan)
|
||||||
|
};
|
||||||
|
|
||||||
|
const passedCount = Object.values(results).filter(r => r.passed).length;
|
||||||
|
|
||||||
|
let grade: ContractGrade;
|
||||||
|
if (passedCount === 7) grade = 'A';
|
||||||
|
else if (passedCount >= 5) grade = 'B';
|
||||||
|
else if (passedCount >= 3) grade = 'C';
|
||||||
|
else if (passedCount >= 1) grade = 'D';
|
||||||
|
else grade = 'F';
|
||||||
|
|
||||||
|
return { grade, results };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 에피타이드 생성
|
||||||
|
*/
|
||||||
|
export function createEpitaph(
|
||||||
|
plan: Plan,
|
||||||
|
validationResult: { grade: ContractGrade; results: Record<string, ClauseResult> },
|
||||||
|
executionResult: { status: 'success' | 'partial' | 'failure'; duration: number; deviationFromPlan?: string }
|
||||||
|
): Epitaph {
|
||||||
|
return {
|
||||||
|
planId: plan.id,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
grade: validationResult.grade,
|
||||||
|
clauses: validationResult.results,
|
||||||
|
executionResult
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue