test: harden decision record storage slice

This commit is contained in:
Eve
2026-05-08 13:21:43 +08:00
parent 354c00dea1
commit 30cb206438
6 changed files with 440 additions and 11 deletions

View File

@@ -18,10 +18,24 @@ function assertObjectRecord(value, label) {
}
function sanitizeFileSegment(value, fallback) {
const normalized = String(value ?? '').trim().replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
const normalized = String(value ?? '')
.trim()
.replace(/[^a-zA-Z0-9._-]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/^(?:\.+-*)+/, '')
.replace(/(?:-+\.)+$/g, '')
.replace(/^-+|-+$/g, '');
return normalized || fallback;
}
function assertIsoTimestamp(value, label) {
const normalized = assertNonEmptyString(value, label);
if (Number.isNaN(Date.parse(normalized))) {
throw new Error(`${label} must be a valid ISO-8601 timestamp`);
}
return normalized;
}
export function validateDecisionRecordArtifact(artifact) {
if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) {
throw new Error('decision record artifact must be an object');
@@ -37,14 +51,43 @@ export function validateDecisionRecordArtifact(artifact) {
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');
const source = spec.source == null ? null : assertObjectRecord(spec.source, 'decision record artifact spec.source');
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');
const recordedAt = assertIsoTimestamp(metadata.recorded_at, 'decision record artifact metadata.recorded_at');
const metadataPolicyId = assertNonEmptyString(metadata.policy_id, 'decision record artifact metadata.policy_id');
const metadataDecision = assertNonEmptyString(metadata.decision, 'decision record artifact metadata.decision');
const decisionPolicyId = assertNonEmptyString(decision.policy_id, 'decision record artifact spec.decision.policy_id');
const decisionName = assertNonEmptyString(decision.decision, 'decision record artifact spec.decision.decision');
assertNonEmptyString(receipt.delivery_state, 'decision record artifact spec.receipt.delivery_state');
if (metadataPolicyId !== decisionPolicyId) {
throw new Error('decision record artifact policy_id mismatch between metadata and spec.decision');
}
if (metadataDecision !== decisionName) {
throw new Error('decision record artifact decision mismatch between metadata and spec.decision');
}
if (source?.event_id != null) {
assertNonEmptyString(source.event_id, 'decision record artifact spec.source.event_id');
}
if (source?.task_id != null) {
const sourceTaskId = assertNonEmptyString(source.task_id, 'decision record artifact spec.source.task_id');
if (metadata.task_id != null && assertNonEmptyString(metadata.task_id, 'decision record artifact metadata.task_id') !== sourceTaskId) {
throw new Error('decision record artifact task_id mismatch between metadata and spec.source');
}
}
if (source?.correlation_id != null) {
const sourceCorrelationId = assertNonEmptyString(source.correlation_id, 'decision record artifact spec.source.correlation_id');
if (metadata.correlation_id != null && assertNonEmptyString(metadata.correlation_id, 'decision record artifact metadata.correlation_id') !== sourceCorrelationId) {
throw new Error('decision record artifact correlation_id mismatch between metadata and spec.source');
}
}
metadata.recorded_at = recordedAt;
metadata.policy_id = metadataPolicyId;
metadata.decision = metadataDecision;
decision.policy_id = decisionPolicyId;
decision.decision = decisionName;
return artifact;
}

View File

@@ -12,5 +12,6 @@ export {
createDecisionRecordArtifact,
createDecisionRecordFileName,
validateDecisionRecordArtifact,
__testables,
} from './decision-artifact.mjs';
export { createFileDecisionStore } from './decision-store.mjs';