diff --git a/packages/compliance/src/dashboards/control_posture.dashboard.ts b/packages/compliance/src/dashboards/control_posture.dashboard.ts index b76b1b8..7b896f0 100644 --- a/packages/compliance/src/dashboards/control_posture.dashboard.ts +++ b/packages/compliance/src/dashboards/control_posture.dashboard.ts @@ -96,8 +96,10 @@ export const ControlPostureDashboard: Dashboard = { values: ['assessment_count'], title: 'Assessments Completed by Month (last 12 months)', type: 'line', + // "Completed" = any concluded assessment. There is no `complete` status; + // the real terminal results are passed / partial / failed. filter: { - status: 'complete', + status: { $in: ['passed', 'partial', 'failed'] }, assessed_at: { $gte: '{12_months_ago}' }, }, compareTo: 'previousYear', diff --git a/packages/compliance/src/data/index.ts b/packages/compliance/src/data/index.ts index 8a49c00..30dd567 100644 --- a/packages/compliance/src/data/index.ts +++ b/packages/compliance/src/data/index.ts @@ -44,6 +44,14 @@ const frameworks = defineSeed(Framework, { version: '2016', status: 'active', }, + { + short_name: 'HIPAA', + full_name: 'HIPAA Security Rule — 45 CFR Part 164 Subpart C', + family: 'hipaa', + version: '2013', + status: 'active', + next_audit_date: cel`daysFromNow(210)`, + }, ], }); @@ -117,6 +125,29 @@ const controls = defineSeed(Control, { description: 'Appropriate technical & organisational measures to ensure security of personal data. Past review-frequency window.', }, + { + code: '164.308(a)(1)', + title: 'Security Management Process', + framework: 'HIPAA', + category: 'risk', + criticality: 'high', + last_status: 'passed', + review_frequency_days: 365, + last_assessed_at: cel`daysAgo(40)`, + description: + 'Implement policies to prevent, detect, contain, and correct security violations (risk analysis + risk management).', + }, + { + code: '164.312(a)(1)', + title: 'Access Control (ePHI)', + framework: 'HIPAA', + category: 'access', + criticality: 'high', + last_status: 'not_tested', + review_frequency_days: 180, + description: + 'Technical policies allowing only authorized persons to access electronic protected health information.', + }, ], }); @@ -206,6 +237,7 @@ const assessments = defineSeed(Assessment, { status: 'failed', finding: 'Two high-severity CVEs open past 30-day SLA on edge servers.', remediation_plan: 'Patch edge servers in next maintenance window (2026-04-15).', + remediation_status: 'open', remediation_due: cel`daysFromNow(14)`, }, { @@ -216,6 +248,7 @@ const assessments = defineSeed(Assessment, { status: 'partial', finding: '3 of 25 PRs sampled lacked documented reviewer.', remediation_plan: 'Enforce required-reviewer via repo settings; re-test in Q2.', + remediation_status: 'in_progress', remediation_due: cel`daysFromNow(30)`, }, { diff --git a/packages/compliance/src/objects/compliance_assessment.object.ts b/packages/compliance/src/objects/compliance_assessment.object.ts index 71a7118..ebc6600 100644 --- a/packages/compliance/src/objects/compliance_assessment.object.ts +++ b/packages/compliance/src/objects/compliance_assessment.object.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack contributors. Apache-2.0 license. import { ObjectSchema, Field } from '@objectstack/spec/data'; -import { P, tmpl } from '@objectstack/spec'; +import { P, F, tmpl } from '@objectstack/spec'; /** * Assessment — a discrete test of a control. Each control should have * one per `review_frequency_days`. Result rolls up onto Control.last_status. @@ -52,7 +52,22 @@ export const Assessment = ObjectSchema.create({ label: 'Remediation Plan', description: 'Required when status is failed or partial.', }), + remediation_status: Field.select({ + label: 'Remediation Status', + description: 'Tracks the fix for a failed/partial finding to closure.', + options: [ + { label: 'Open', value: 'open', color: '#EF4444' }, + { label: 'In Progress', value: 'in_progress', color: '#F59E0B' }, + { label: 'Resolved', value: 'resolved', color: '#10B981' }, + { label: 'Risk Accepted', value: 'risk_accepted', color: '#6B7280' }, + ], + }), remediation_due: Field.date({ label: 'Remediation Due' }), + is_remediation_overdue: Field.formula({ + label: 'Remediation Overdue', + description: 'Past the remediation due date and not yet resolved / risk-accepted.', + expression: F`record.remediation_due != null && record.remediation_due < today() && record.remediation_status != "resolved" && record.remediation_status != "risk_accepted"`, + }), }, enable: { @@ -74,7 +89,7 @@ export const Assessment = ObjectSchema.create({ ], titleFormat: tmpl`{{record.title}}`, - compactLayout: ['title', 'control', 'status', 'cycle', 'assessed_at'], + compactLayout: ['title', 'control', 'status', 'remediation_status', 'cycle', 'assessed_at'], validations: [ { diff --git a/packages/compliance/src/translations/en.ts b/packages/compliance/src/translations/en.ts index c37d150..961572f 100644 --- a/packages/compliance/src/translations/en.ts +++ b/packages/compliance/src/translations/en.ts @@ -162,11 +162,22 @@ export const en: TranslationData = { }, finding: { label: 'Finding' }, remediation_plan: { label: 'Remediation Plan' }, + remediation_status: { + label: 'Remediation Status', + options: { + open: 'Open', + in_progress: 'In Progress', + resolved: 'Resolved', + risk_accepted: 'Risk Accepted', + }, + }, remediation_due: { label: 'Remediation Due' }, + is_remediation_overdue: { label: 'Remediation Overdue' }, }, _views: { all_assessments: { label: 'All Assessments' }, failed_assessments: { label: 'Failed Assessments' }, + open_remediations: { label: 'Open Remediations' }, in_progress_assessments: { label: 'In Progress' }, }, }, @@ -229,8 +240,6 @@ export const en: TranslationData = { title: 'Assessments In Progress', description: 'Assessments currently being conducted', }, - failing_table: { title: 'Failing Controls (Action Required)' }, - expiring_evidence_table: { title: 'Evidence Expiring Soon' }, }, }, }, diff --git a/packages/compliance/src/translations/zh-CN.ts b/packages/compliance/src/translations/zh-CN.ts index 70b1fa9..fe58b9b 100644 --- a/packages/compliance/src/translations/zh-CN.ts +++ b/packages/compliance/src/translations/zh-CN.ts @@ -148,11 +148,22 @@ export const zhCN: TranslationData = { }, finding: { label: '审计发现' }, remediation_plan: { label: '整改计划' }, + remediation_status: { + label: '整改状态', + options: { + open: '待整改', + in_progress: '整改中', + resolved: '已整改', + risk_accepted: '接受风险', + }, + }, remediation_due: { label: '整改截止' }, + is_remediation_overdue: { label: '整改逾期' }, }, _views: { all_assessments: { label: '全部评估' }, failed_assessments: { label: '未通过评估' }, + open_remediations: { label: '待整改项' }, in_progress_assessments: { label: '进行中' }, }, }, @@ -203,8 +214,6 @@ export const zhCN: TranslationData = { failing_controls: { title: '未通过或部分通过' }, expiring_evidence: { title: '30 天内到期佐证' }, in_progress_assessments: { title: '进行中评估' }, - failing_table: { title: '需整改的控制项' }, - expiring_evidence_table: { title: '即将到期的佐证' }, }, }, }, diff --git a/packages/compliance/src/views/compliance_assessment.view.ts b/packages/compliance/src/views/compliance_assessment.view.ts index 0fd2f33..b96c3c4 100644 --- a/packages/compliance/src/views/compliance_assessment.view.ts +++ b/packages/compliance/src/views/compliance_assessment.view.ts @@ -24,6 +24,12 @@ export const AssessmentViews = defineView({ tabs: [ { name: 'all', label: 'All', view: 'all_assessments', isDefault: true, pinned: true }, { name: 'failed', label: 'Failed', icon: 'alert-triangle', view: 'failed_assessments' }, + { + name: 'open_remediations', + label: 'Open Remediations', + icon: 'wrench', + view: 'open_remediations', + }, { name: 'in_progress', label: 'In Progress', icon: 'play', view: 'in_progress_assessments' }, ], }, @@ -39,6 +45,16 @@ export const AssessmentViews = defineView({ sort: [{ field: 'remediation_due', order: 'asc' }], }, + open_remediations: { + name: 'open_remediations', + type: 'grid', + label: 'Open Remediations', + data: { provider: 'object', object: 'compliance_assessment' }, + columns: ['title', 'control', 'remediation_status', 'remediation_due', 'assessor'], + filter: [{ field: 'remediation_status', operator: 'in', value: ['open', 'in_progress'] }], + sort: [{ field: 'remediation_due', order: 'asc' }], + }, + in_progress_assessments: { name: 'in_progress_assessments', type: 'grid', @@ -67,7 +83,15 @@ export const AssessmentViews = defineView({ ], }, { label: 'Finding', columns: 1, fields: ['finding'] }, - { label: 'Remediation', columns: 2, fields: ['remediation_due', 'remediation_plan'] }, + { + label: 'Remediation', + columns: 2, + fields: [ + 'remediation_status', + 'remediation_due', + { field: 'remediation_plan', colSpan: 2 }, + ], + }, ], }, });