/home/techb158/cosmic.abdallabala.com/src/services
NameSizeModeActions
dashboardService.js21770666editdlrm
gateWorkflowService.js72960666editdlrm
integrationService.js274610666editdlrm
oauthService.js92820666editdlrm
reportingService.js186700666editdlrm
Edit: /home/techb158/cosmic.abdallabala.com/src/services/gateWorkflowService.js (7296B)
const { list, findById, insert, update, audit } = require("../storage/jsonDatabase.js"); const GATE_DECISIONS = ["Approved", "Rejected", "Accepted", "Needs changes"]; const CRITERION_REVIEW_STATUSES = ["Open", "Reviewed", "Evidence requested", "Accepted", "Rejected"]; function safeParseJson(value) { if (!value || typeof value !== "string") return value || null; try { return JSON.parse(value); } catch (_error) { return value; } } function sortDescByDate(a, b, field) { return String(b[field] || b.created_at || "").localeCompare(String(a[field] || a.created_at || "")); } function normalizeDecision(value) { const decision = value || "Needs changes"; if (!GATE_DECISIONS.includes(decision)) { throw new Error(`decision must be one of: ${GATE_DECISIONS.join(", ")}`); } return decision; } function normalizeCriterionStatus(value) { const status = value || "Reviewed"; if (!CRITERION_REVIEW_STATUSES.includes(status)) { throw new Error(`reviewer_status must be one of: ${CRITERION_REVIEW_STATUSES.join(", ")}`); } return status; } function toCriterionView(row) { return { id: row.id, gateId: row.gate_id, name: row.name, actualValue: row.actual_value, expectedRule: row.expected_rule, status: row.status, blocking: Boolean(row.blocking), evidence: row.evidence || "", evidenceTitle: row.evidence_title || "", evidenceUri: row.evidence_uri || "", reviewerStatus: row.reviewer_status || "Open", reviewerNote: row.reviewer_note || "", createdAt: row.created_at, updatedAt: row.updated_at }; } function toDecisionView(row) { return { id: row.id, gateId: row.gate_id, decision: row.decision, reason: row.reason || "", approvedByUserId: row.approved_by_user_id || null, reviewerName: row.reviewer_display_name || row.approved_by_user_id || "Governance Reviewer", approvedAt: row.approved_at || row.created_at, createdAt: row.created_at, updatedAt: row.updated_at }; } function toGateView(database, gate) { const criteria = list(database, "gate_criteria", row => row.gate_id === gate.id) .sort((a, b) => String(a.name || a.id).localeCompare(String(b.name || b.id))) .map(toCriterionView); const decisions = list(database, "gate_decisions", row => row.gate_id === gate.id) .sort((a, b) => sortDescByDate(a, b, "approved_at")) .map(toDecisionView); const blockingCriteria = criteria.filter(item => item.blocking || item.status === "Fail").length; const reviewerNotes = criteria.filter(item => item.reviewerNote).length; const evidenceLinks = criteria.filter(item => item.evidenceUri).length; return { id: gate.id, projectId: gate.project_id, status: gate.status, reviewStatus: gate.review_status || "Pending review", summary: gate.summary || "", evaluatedByUserId: gate.evaluated_by_user_id || "system", evaluatedAt: gate.evaluated_at || gate.created_at, reviewedBy: gate.reviewed_by_display_name || "", reviewedAt: gate.reviewed_at || null, latestDecision: decisions[0] || null, blockingCriteria, reviewerNotes, evidenceLinks, criteria, decisions, createdAt: gate.created_at, updatedAt: gate.updated_at }; } class GateWorkflowService { constructor(database) { this.database = database; } listGateHistory(projectId) { const db = this.database.read(); return list(db, "deployment_gates", gate => gate.project_id === projectId) .sort((a, b) => sortDescByDate(a, b, "evaluated_at")) .map(gate => toGateView(db, gate)); } getGate(gateId) { const db = this.database.read(); const gate = findById(db, "deployment_gates", gateId); return gate ? toGateView(db, gate) : null; } addDecision(gateId, payload, actorUserId = "system") { if (!payload || typeof payload !== "object") throw new Error("Gate decision payload is required"); const decision = normalizeDecision(payload.decision); const reason = payload.reason || payload.note || ""; const reviewerName = payload.reviewerName || payload.reviewer_display_name || "Governance Reviewer"; return this.database.transaction(db => { const gate = findById(db, "deployment_gates", gateId); if (!gate) return null; const approvedAt = new Date().toISOString(); const row = insert(db, "gate_decisions", { gate_id: gateId, decision, reason, approved_by_user_id: actorUserId, reviewer_display_name: reviewerName, approved_at: approvedAt }, "GDEC"); const updatedGate = update(db, "deployment_gates", gateId, { review_status: decision, reviewed_by_display_name: reviewerName, reviewed_at: approvedAt }); audit(db, { project_id: gate.project_id, actor_user_id: actorUserId, entity_type: "GateDecision", entity_id: row.id, action: "create", after_json: row }); return toGateView(db, updatedGate); }); } updateCriterion(criterionId, payload, actorUserId = "system") { if (!payload || typeof payload !== "object") throw new Error("Gate criterion payload is required"); const patch = {}; if (payload.reviewerNote !== undefined || payload.reviewer_note !== undefined) { patch.reviewer_note = payload.reviewerNote || payload.reviewer_note || ""; } if (payload.reviewerStatus !== undefined || payload.reviewer_status !== undefined) { patch.reviewer_status = normalizeCriterionStatus(payload.reviewerStatus || payload.reviewer_status); } if (payload.evidenceTitle !== undefined || payload.evidence_title !== undefined) { patch.evidence_title = payload.evidenceTitle || payload.evidence_title || ""; } if (payload.evidenceUri !== undefined || payload.evidence_uri !== undefined) { patch.evidence_uri = payload.evidenceUri || payload.evidence_uri || ""; } return this.database.transaction(db => { const before = findById(db, "gate_criteria", criterionId); if (!before) return null; const criterion = update(db, "gate_criteria", criterionId, patch); const gate = findById(db, "deployment_gates", criterion.gate_id); audit(db, { project_id: gate ? gate.project_id : "unknown", actor_user_id: actorUserId, entity_type: "GateCriterion", entity_id: criterionId, action: "update", before_json: before, after_json: criterion }); return toCriterionView(criterion); }); } listAuditEvents(projectId, limit = 80) { const db = this.database.read(); return list(db, "audit_events", event => event.project_id === projectId) .sort((a, b) => sortDescByDate(a, b, "created_at")) .slice(0, Number(limit) || 80) .map(event => ({ id: event.id, projectId: event.project_id, actorUserId: event.actor_user_id, entityType: event.entity_type, entityId: event.entity_id, action: event.action, before: safeParseJson(event.before_json), after: safeParseJson(event.after_json), createdAt: event.created_at })); } } module.exports = { GateWorkflowService, toGateView, GATE_DECISIONS, CRITERION_REVIEW_STATUSES };