+
+
+
+
+
+
+
+
+
+
@@ -263,7 +306,9 @@ const props = defineProps<{
const emit = defineEmits<{
cancel: [];
- created: [risk: Risk];
+ // The SSP-scoped register create responds with a register risk carrying an
+ // `id`; the plain OSCAL create does not, hence the optional field.
+ created: [risk: Risk & { id?: string }];
}>();
const toast = useToast();
@@ -276,6 +321,14 @@ const riskStatusOptions = [
{ value: 'closed', label: 'Closed' },
];
+const riskLevelOptions = [
+ { value: 'negligible', label: 'Negligible' },
+ { value: 'low', label: 'Low' },
+ { value: 'moderate', label: 'Moderate' },
+ { value: 'high', label: 'High' },
+ { value: 'critical', label: 'Critical' },
+];
+
const riskContext = computed
(() => {
if (props.sspId) {
return {
@@ -332,9 +385,20 @@ const formData = reactive({
description: '',
statement: '',
status: '',
+ likelihood: '',
+ impact: '',
deadline: '',
remarks: '',
});
+
+function normalizeRiskLevel(value?: string | null): string {
+ const normalized = value?.trim().toLowerCase();
+ if (!normalized) return '';
+ const canonical = normalized === 'medium' ? 'moderate' : normalized;
+ return riskLevelOptions.some((level) => level.value === canonical)
+ ? canonical
+ : '';
+}
const showTemplateSelector = ref(false);
const selectedTemplate = ref(null);
const selectedTemplateName = ref('');
@@ -488,6 +552,8 @@ function applyTemplate(template: RiskTemplate) {
formData.title = template.title;
formData.description = statement;
formData.statement = statement;
+ formData.likelihood = normalizeRiskLevel(template.likelihoodHint);
+ formData.impact = normalizeRiskLevel(template.impactHint);
selectedTemplate.value = template;
const templateRemarks = buildTemplateRemarks(template);
@@ -579,8 +645,8 @@ async function submit() {
description: formData.description,
statement: formData.statement,
status: formData.status,
- likelihood: toOptionalString(selectedTemplate.value?.likelihoodHint),
- impact: toOptionalString(selectedTemplate.value?.impactHint),
+ likelihood: toOptionalString(formData.likelihood),
+ impact: toOptionalString(formData.impact),
threatIds: selectedTemplate.value
? getThreatIdsFromTemplate(selectedTemplate.value)
: undefined,
diff --git a/src/composables/useLineage/fixtures.ts b/src/composables/useLineage/fixtures.ts
index 3ee05378..b55496a3 100644
--- a/src/composables/useLineage/fixtures.ts
+++ b/src/composables/useLineage/fixtures.ts
@@ -426,6 +426,8 @@ const riskCredLeak = node({
likelihood: 'likely',
impact: 'high',
linkedEvidenceCount: 2,
+ sspId: 'demo-ssp-acme',
+ sspTitle: 'Acme Production',
reviewDeadline: '2026-08-15T00:00:00Z',
firstSeenAt: '2026-05-02T00:00:00Z',
lastSeenAt: '2026-07-01T00:00:00Z',
@@ -455,6 +457,8 @@ const riskStaleAccess = node({
likelihood: 'possible',
impact: 'critical',
linkedEvidenceCount: 0,
+ sspId: 'demo-ssp-globex',
+ sspTitle: 'Globex Staging',
risk: {
openScoreSum: 42,
mutedScoreSum: 0,
diff --git a/src/composables/useLineage/types.ts b/src/composables/useLineage/types.ts
index 051c2ba1..fc0f9343 100644
--- a/src/composables/useLineage/types.ts
+++ b/src/composables/useLineage/types.ts
@@ -138,6 +138,9 @@ export interface LineageNode {
lastReviewedAt?: string;
firstSeenAt?: string;
lastSeenAt?: string;
+ /** The single SSP this risk belongs to (risks always have exactly one). */
+ sspId?: string;
+ sspTitle?: string;
// --- evidence nodes (nodeType === 'evidence') ---
evidenceId?: string;
diff --git a/src/views/evidence/CreateView.vue b/src/views/evidence/CreateView.vue
index 3e01aea9..bac343c6 100644
--- a/src/views/evidence/CreateView.vue
+++ b/src/views/evidence/CreateView.vue
@@ -19,7 +19,6 @@ import type {
EvidenceStatus,
} from '@/stores/evidence.ts';
import router from '@/router';
-import { v4 as uuidv4 } from 'uuid';
import EvidenceForm from './partial/EvidenceForm.vue';
import type { BackMatterResource } from '@/oscal';
@@ -35,9 +34,7 @@ const { data: createdEvidence, execute: createEvidence } = useDataApi(
);
const backmatterResources = ref([]);
-const evidence = ref>({
- uuid: uuidv4(),
-});
+const evidence = ref>({});
async function submit(
updatedEvidence: Partial,
diff --git a/src/views/evidence/UpdateView.vue b/src/views/evidence/UpdateView.vue
index 33f8fcd8..e2109283 100644
--- a/src/views/evidence/UpdateView.vue
+++ b/src/views/evidence/UpdateView.vue
@@ -1,7 +1,8 @@
- Update Evidence
+ Re-submit Evidence
- Update the details of an existing piece of evidence.
+ Submit a new revision of this evidence. The revision joins the same evidence
+ stream and becomes the latest record.
diff --git a/src/views/evidence/ViewView.vue b/src/views/evidence/ViewView.vue
index 2980699e..1ddfc725 100644
--- a/src/views/evidence/ViewView.vue
+++ b/src/views/evidence/ViewView.vue
@@ -25,7 +25,7 @@
Back to Execution
- Update Evidence
+ Re-submit
@@ -348,6 +348,153 @@
+