CarboAny / src /lib /qaqcStore.ts
Esketch's picture
deploy: Campaign Control Plane + Security Hardening Release (Orphan Clean Build v3)
5c98911
Raw
History Blame Contribute Delete
3.71 kB
// QA/QC Audit & Issue Tracking Store
// Manages history of validations and identified defects.
export type AuditResult = 'pass' | 'fail' | 'minor_issue';
export type IssueStatus = 'open' | 'fixed' | 'verified';
export interface AuditLog {
id: string;
scenarioId: string;
scenarioTitle: string;
timestamp: string;
performer: string;
result: AuditResult;
notes: string;
relatedIssueId?: string;
}
export interface SystemIssue {
id: string;
title: string;
description: string;
severity: 'high' | 'medium' | 'low';
status: IssueStatus;
domain: string;
createdAt: string;
resolvedAt?: string;
}
const LOG_KEY = 'carboany_audit_logs';
const ISSUE_KEY = 'carboany_system_issues';
/* --- Audit Logs API --- */
export function getAuditLogs(): AuditLog[] {
const raw = localStorage.getItem(LOG_KEY);
if (!raw) return [];
const logs = JSON.parse(raw) as AuditLog[];
// Self-heal: ์ด์ „ Date.now() ๊ธฐ๋ฐ˜ id๊ฐ€ ์ค‘๋ณต๋œ ๊ฒฝ์šฐ UUID๋กœ ์žฌ๋ฐœ๊ธ‰
const seen = new Set<string>();
let mutated = false;
for (const log of logs) {
if (!log.id || seen.has(log.id)) {
log.id = newId('log');
mutated = true;
}
seen.add(log.id);
}
if (mutated) {
localStorage.setItem(LOG_KEY, JSON.stringify(logs));
}
return logs;
}
function newId(prefix: string): string {
// crypto.randomUUID()๋Š” ๋™๊ธฐ ๋ฃจํ”„์—์„œ๋„ ์ถฉ๋Œํ•˜์ง€ ์•Š์Œ. Date.now ๊ธฐ๋ฐ˜์€ ms ๋‹จ์œ„ ์ค‘๋ณต ์œ„ํ—˜.
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return `${prefix}-${crypto.randomUUID()}`;
}
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
}
export function recordAudit(log: Omit<AuditLog, 'id' | 'timestamp'>): AuditLog {
const logs = getAuditLogs();
const newLog: AuditLog = {
...log,
id: newId('log'),
timestamp: new Date().toISOString(),
};
logs.unshift(newLog); // Newest first
localStorage.setItem(LOG_KEY, JSON.stringify(logs));
return newLog;
}
/* --- Issues API --- */
export function getSystemIssues(): SystemIssue[] {
const raw = localStorage.getItem(ISSUE_KEY);
return raw ? JSON.parse(raw) : [];
}
export function reportIssue(issue: Omit<SystemIssue, 'id' | 'createdAt' | 'status'>): SystemIssue {
const issues = getSystemIssues();
const newIssue: SystemIssue = {
...issue,
id: newId('issue'),
status: 'open',
createdAt: new Date().toISOString(),
};
issues.unshift(newIssue);
localStorage.setItem(ISSUE_KEY, JSON.stringify(issues));
return newIssue;
}
export function updateIssueStatus(id: string, status: IssueStatus): void {
const issues = getSystemIssues();
const idx = issues.findIndex(i => i.id === id);
if (idx >= 0) {
issues[idx].status = status;
if (status === 'fixed' || status === 'verified') {
issues[idx].resolvedAt = new Date().toISOString();
}
localStorage.setItem(ISSUE_KEY, JSON.stringify(issues));
}
}
/* --- Initial Seed for History (Previously done tasks) --- */
export function seedPreviousHistory() {
if (getAuditLogs().length > 0) return;
const prevTasks = [
{ sid: 'p-01', title: '์‚ฌ์šฉ์ž ์˜จ๋ณด๋”ฉ', result: 'pass' as const, notes: 'AuthContext ๋‚ด ์ž๋™ ์ฐธ์—ฌ ๋กœ์ง ํ™•์ธ ์™„๋ฃŒ' },
{ sid: 'a-05', title: '๊ด€๋ฆฌ์ž ๋Œ€์‹œ๋ณด๋“œ ์‹ค๋ฐ์ดํ„ฐ', result: 'pass' as const, notes: 'Stage 1-3 LIVE ์—ฐ๋™ ํ™•์ธ' },
{ sid: 's-03', title: 'ESG ๋ณด๊ณ ์„œ โ†” Scope3 ์—ฐ๋™', result: 'pass' as const, notes: 'SME ๊ธฐ๋ถ€ ์‹ค์  ์˜ค๋ฒ„๋ ˆ์ด ์„ฑ๊ณต' },
];
prevTasks.forEach(t => {
recordAudit({
scenarioId: t.sid,
scenarioTitle: t.title,
performer: 'Gemini CLI',
result: t.result,
notes: t.notes
});
});
}