Add plain-language status doc and minimal decision store contract
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const EXPECTED_KIND = 'DecisionRecordArtifact';
|
||||
const EXPECTED_API_VERSION = 'reporting-governance/v1alpha1';
|
||||
|
||||
function assertNonEmptyString(value, label) {
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
throw new Error(`${label} must be a non-empty string`);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function assertObjectRecord(value, label) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(`${label} must be an object record`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sanitizeFileSegment(value, fallback) {
|
||||
const normalized = String(value ?? '').trim().replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
||||
return normalized || fallback;
|
||||
}
|
||||
|
||||
export function validateDecisionRecordArtifact(artifact) {
|
||||
if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) {
|
||||
throw new Error('decision record artifact must be an object');
|
||||
}
|
||||
if (artifact.kind !== EXPECTED_KIND) {
|
||||
throw new Error(`decision record artifact kind must be ${EXPECTED_KIND}`);
|
||||
}
|
||||
if (artifact.apiVersion !== EXPECTED_API_VERSION) {
|
||||
throw new Error(`decision record artifact apiVersion must be ${EXPECTED_API_VERSION}`);
|
||||
}
|
||||
|
||||
const metadata = assertObjectRecord(artifact.metadata, 'decision record artifact metadata');
|
||||
const spec = assertObjectRecord(artifact.spec, 'decision record artifact spec');
|
||||
const decision = assertObjectRecord(spec.decision, 'decision record artifact spec.decision');
|
||||
const receipt = assertObjectRecord(spec.receipt, 'decision record artifact spec.receipt');
|
||||
|
||||
assertNonEmptyString(metadata.recorded_at, 'decision record artifact metadata.recorded_at');
|
||||
assertNonEmptyString(metadata.policy_id, 'decision record artifact metadata.policy_id');
|
||||
assertNonEmptyString(metadata.decision, 'decision record artifact metadata.decision');
|
||||
assertNonEmptyString(decision.policy_id, 'decision record artifact spec.decision.policy_id');
|
||||
assertNonEmptyString(decision.decision, 'decision record artifact spec.decision.decision');
|
||||
assertNonEmptyString(receipt.delivery_state, 'decision record artifact spec.receipt.delivery_state');
|
||||
|
||||
return artifact;
|
||||
}
|
||||
|
||||
export function createDecisionRecordArtifact({ decision, receipt, recordedAt = new Date().toISOString(), source = {} } = {}) {
|
||||
const normalizedDecision = assertObjectRecord(decision, 'decision');
|
||||
const normalizedReceipt = assertObjectRecord(receipt, 'receipt');
|
||||
const policyId = assertNonEmptyString(normalizedDecision.policy_id, 'decision.policy_id');
|
||||
const decisionName = assertNonEmptyString(normalizedDecision.decision, 'decision.decision');
|
||||
|
||||
return validateDecisionRecordArtifact({
|
||||
kind: EXPECTED_KIND,
|
||||
apiVersion: EXPECTED_API_VERSION,
|
||||
metadata: {
|
||||
record_id: `dec_${crypto.randomUUID()}`,
|
||||
recorded_at: assertNonEmptyString(recordedAt, 'recordedAt'),
|
||||
policy_id: policyId,
|
||||
decision: decisionName,
|
||||
correlation_id: source.correlation_id ?? null,
|
||||
task_id: source.task_id ?? null,
|
||||
event_id: source.event_id ?? null,
|
||||
},
|
||||
spec: {
|
||||
decision: normalizedDecision,
|
||||
receipt: normalizedReceipt,
|
||||
source: {
|
||||
event_id: source.event_id ?? null,
|
||||
task_id: source.task_id ?? null,
|
||||
correlation_id: source.correlation_id ?? null,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createDecisionRecordFileName(artifact) {
|
||||
const validatedArtifact = validateDecisionRecordArtifact(artifact);
|
||||
return [
|
||||
validatedArtifact.metadata.recorded_at.replace(/[.:]/g, '-'),
|
||||
sanitizeFileSegment(validatedArtifact.metadata.policy_id, 'policy'),
|
||||
sanitizeFileSegment(validatedArtifact.metadata.decision, 'decision'),
|
||||
`${validatedArtifact.metadata.record_id}.json`,
|
||||
].join('-');
|
||||
}
|
||||
|
||||
export const __testables = {
|
||||
EXPECTED_KIND,
|
||||
EXPECTED_API_VERSION,
|
||||
sanitizeFileSegment,
|
||||
};
|
||||
Reference in New Issue
Block a user