import { baselineInputIssues, documentInputIssues } from './access.js';
import { canApprove, bumpVersion, log, timestamp, uid, resolveSelection } from './store.jsx';
const approved = baseline => baseline && ['Active', 'Approved'].includes(baseline.status);
const sourcesFor = (state, ids) => ids.map(id => {
const doc = state.documents.find(item => item.id === id);
if (!doc) throw new Error('A baseline document is no longer available.');
return { id, revision: doc.revision, checksum: doc.checksum || 'synthetic-sample' };
});
export function requestBaseline(state, values, documents, workspaceId = '', parentId = '') {
if (!state.session.authenticated) throw new Error('Sign in before submitting a baseline.');
const parent = state.baselines.find(item => item.id === parentId);
const workspace = state.workspaces.find(item => item.id === workspaceId && item.team === state.session.team);
if (parentId && (!approved(parent) || parent.team !== state.session.team)) throw new Error('Choose an approved System Baseline to edit.');
if (state.candidates.some(item => item.status === 'Pending review' && (parentId ? item.parentId === parentId : workspaceId && item.workspaceId === workspaceId))) throw new Error('An approval request is already pending for this baseline or workspace.');
const issues = parent ? documentInputIssues(documents) : baselineInputIssues(state, values.ref, documents);
if (issues.length) throw new Error(issues.join(' '));
const sourceIds = documents.map(doc => doc.id);
if (parent && JSON.stringify([...parent.sourceIds].sort()) === JSON.stringify([...sourceIds].sort()) && parent.description === values.description && (parent.scope || '') === (values.scope || '')) throw new Error('Add or remove a document, or update the baseline details.');
documents.forEach(doc => { if (!state.documents.some(item => item.id === doc.id)) state.documents.push({ ...doc, status: 'Ready' }); });
const baseline = { id: uid('baseline'), ref: parent?.ref || values.ref.trim(), team: state.session.team, description: values.description || '', scope: values.scope || '', version: parent ? `${Number(parent.version.split('.')[0]) + 1}.0` : '1.0', status: 'Pending approval', sourceIds, rules: (parent?.rules || []).filter(rule => sourceIds.includes(rule.source)), incorporatedCRs: [...parent?.incorporatedCRs || []], parentId: parent?.id || null, createdAt: timestamp() };
state.baselines.push(baseline);
const request = { id: uid('candidate'), type: parent ? 'edit' : 'create', baselineId: baseline.id, parentId: parent?.id || null, workspaceId: workspace?.id || '', team: state.session.team, ref: baseline.ref, description: baseline.description, crIds: [], revision: 1, status: 'Pending review', createdAt: timestamp(), decisions: [], manifest: { baseline: structuredClone(baseline), sources: sourcesFor(state, sourceIds), previousSourceIds: [...parent?.sourceIds || []] } };
state.candidates.unshift(request);
if (workspace) workspace.pendingBaselineId = baseline.id;
log(state, `Submitted System Baseline ${parent ? 'edit' : 'creation'}: ${baseline.ref}`);
return request;
}
export function approvalBlockers(state, request) {
if (!request) return [];
if (!request.manifest?.baseline) {
const issues = [...resolveSelection(state, request.parentId, request.crIds || []).issues];
if ((request.crIds || []).some(id => !state.crs.find(cr => cr.id === id)?.changes.length)) issues.push('Add source-linked changes to each selected CR before requesting a baseline update.');
for (const source of request.manifest?.sources || []) {
const doc = state.documents.find(item => item.id === source.id && item.team === request.team);
if (source.revision !== undefined && (!doc || doc.revision !== source.revision || (doc.checksum || 'synthetic-sample') !== source.checksum)) issues.push('A submitted document changed or is unavailable. Submit a fresh request.');
}
for (const cr of request.manifest?.changes || []) if (state.crs.find(item => item.id === cr.id)?.revision !== cr.revision) issues.push('A submitted CR changed. Submit a fresh merge request.');
return [...new Set(issues)];
}
const issues = [];
if (!request.manifest.sources.length) issues.push('Keep at least one baseline document.');
for (const source of request.manifest.sources) {
const doc = state.documents.find(item => item.id === source.id && item.team === request.team);
if (!doc || doc.revision !== source.revision || (doc.checksum || 'synthetic-sample') !== source.checksum) issues.push('A submitted document changed or is unavailable. Submit a fresh request.');
}
if (request.parentId && !approved(state.baselines.find(item => item.id === request.parentId))) issues.push('The parent baseline is unavailable.');
return [...new Set(issues)];
}
export function requestBaselineMerge(state, workspaceId, name) {
if (!state.session.authenticated) throw new Error('Sign in before submitting a CR merge.');
const workspace = state.workspaces.find(item => item.id === workspaceId && item.team === state.session.team);
if (!workspace?.crIds.length) throw new Error('Select at least one CR in Knowledge versions.');
const selection = resolveSelection(state, workspace.baselineId, workspace.crIds);
if (selection.issues.length) throw new Error(selection.issues.join(' '));
if (selection.changes.some(cr => !cr.changes.length)) throw new Error('Add source-linked changes to each selected CR.');
if (state.candidates.some(item => item.workspaceId === workspace.id && item.status === 'Pending review')) throw new Error('This workspace already has a pending baseline request.');
const ids = [...new Set([...selection.baseline.sourceIds, ...selection.diff.map(change => change.source)])];
const docs = ids.map(id => state.documents.find(doc => doc.id === id && doc.team === workspace.team));
if (docs.some(doc => !doc || !['Ready', 'Reviewed locally'].includes(doc.status))) throw new Error('Review every selected baseline and CR document before merging.');
const issues = baselineInputIssues(state, name, docs);
if (issues.length) throw new Error(issues.join(' '));
if (name.trim().length > 60) throw new Error('Use a baseline name of 60 characters or fewer.');
const request = { id: uid('candidate'), type: 'changes', ref: name.trim(), description: `Merge ${selection.changes.map(cr => cr.ref).join(', ')} into ${selection.baseline.ref}.`, team: workspace.team, workspaceId: workspace.id, parentId: workspace.baselineId, crIds: [...workspace.crIds], manifest: { parent: structuredClone(selection.baseline), changes: structuredClone(selection.changes), sources: sourcesFor(state, ids), previousSourceIds: [...selection.baseline.sourceIds] }, status: 'Pending review', revision: 1, createdAt: timestamp(), decisions: [] };
state.candidates.unshift(request); log(state, `Submitted CR merge: ${request.ref}`);
return request;
}
export function decideBaseline(state, requestId, decision, rationale = '', hash = '') {
if (!canApprove(state)) throw new Error('Only an Administrator can approve or reject a baseline.');
const request = state.candidates.find(item => item.id === requestId && item.team === state.session.team);
if (!request || request.status !== 'Pending review') throw new Error('This request has already been decided.');
if (!['Approved', 'Rejected'].includes(decision)) throw new Error('Choose an approval decision.');
if (decision === 'Rejected' && !rationale.trim()) throw new Error('Add a reason for rejection.');
if (decision === 'Approved' && approvalBlockers(state, request).length) throw new Error(approvalBlockers(state, request).join(' '));
let baseline = state.baselines.find(item => item.id === request.baselineId);
if (decision === 'Approved') {
if (request.manifest?.baseline) {
if (!baseline || baseline.status !== 'Pending approval') throw new Error('The submitted baseline is unavailable.');
Object.assign(baseline, structuredClone(request.manifest.baseline), { status: 'Approved', manifestHash: hash });
} else {
const selection = resolveSelection(state, request.parentId, request.crIds || []);
const snapshot = request.manifest || { parent: selection.baseline, changes: selection.changes };
const rules = new Map(snapshot.parent.rules.map(rule => [rule.id, rule]));
snapshot.changes.forEach(cr => cr.changes.forEach(change => { if (change.op === 'REMOVE') rules.delete(change.ruleId); else rules.set(change.ruleId, { id: change.ruleId, title: change.title, text: change.after, source: change.source }); }));
baseline = { ...structuredClone(snapshot.parent), id: uid('baseline'), ref: request.ref, description: request.description, version: `${Number(snapshot.parent.version.split('.')[0]) + 1}.0`, parentId: request.parentId, status: 'Approved', sourceIds: [...new Set([...(snapshot.sources?.map(source => source.id) || snapshot.parent.sourceIds), ...[...rules.values()].map(rule => rule.source)])], rules: [...rules.values()], incorporatedCRs: [...new Set([...snapshot.parent.incorporatedCRs, ...request.crIds])], manifestHash: hash, createdAt: timestamp() };
state.baselines.push(baseline); request.baselineId = baseline.id;
}
const workspace = state.workspaces.find(item => item.id === request.workspaceId && item.team === request.team);
if (workspace && (workspace.pendingBaselineId === baseline.id || !workspace.baselineId || workspace.baselineId === request.parentId)) {
const previous = state.baselines.find(item => item.id === workspace.baselineId);
const removed = (previous?.sourceIds || []).filter(id => !baseline.sourceIds.includes(id));
workspace.customRules = (workspace.customRules || []).filter(rule => !removed.includes(rule.source));
workspace.baselineId = baseline.id; workspace.pendingBaselineId = ''; workspace.crIds = workspace.crIds.filter(id => !baseline.incorporatedCRs.includes(id)); workspace.requirementCount = baseline.rules.length + workspace.customRules.length; bumpVersion(state, workspace);
}
} else {
if (baseline) baseline.status = 'Rejected';
const workspace = state.workspaces.find(item => item.id === request.workspaceId);
if (workspace?.pendingBaselineId === request.baselineId) workspace.pendingBaselineId = '';
}
request.status = decision;
request.decisions.push({ decision, actor: state.session.name, at: timestamp(), rationale: rationale.trim(), revision: request.revision, manifestHash: hash });
log(state, `${decision} System Baseline ${request.type === 'create' ? 'creation' : 'edit'}: ${request.ref}`);
return baseline;
}
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)