rea/src/server/sessionEvidence.ts

71 lines
2.2 KiB
TypeScript

import type { BinarySessionPort } from "../application/BinarySession.js";
import {
EvidenceIntegrityError,
EvidenceReferenceError,
type EvidenceLimitError,
} from "../domain/errors.js";
import type { Evidence } from "../domain/evidence.js";
import { err, ok, type Result } from "../domain/result.js";
type EvidenceAuthorityResult = Result<Evidence[], EvidenceIntegrityError>;
/** Resolve bounded IDs to authoritative records with exact semantic identity. */
export const resolveSessionEvidenceIds = (
session: BinarySessionPort,
evidenceIds: readonly string[],
expected: {
readonly operation: string | readonly string[];
readonly predicate: string;
},
): EvidenceAuthorityResult => {
const records: Evidence[] = [];
const expectedOperations = Array.isArray(expected.operation)
? expected.operation
: [expected.operation];
const expectedOperationLabel = expectedOperations.join(" or ");
for (const evidenceId of evidenceIds) {
const record = session.evidenceById(evidenceId);
if (record === undefined)
return err(
new EvidenceReferenceError(
evidenceId,
"missing",
expectedOperationLabel,
null,
),
);
if (!expectedOperations.includes(record.operation))
return err(
new EvidenceReferenceError(
evidenceId,
"wrong_operation",
expectedOperationLabel,
record.operation,
),
);
if (record.predicate_type !== expected.predicate)
return err(
new EvidenceReferenceError(
evidenceId,
"wrong_predicate",
expected.predicate,
record.predicate_type,
),
);
records.push(record);
}
return ok(records);
};
/** Record source evidence in order, stopping at the first session rejection. */
export const recordSessionEvidenceSources = (
recordEvidence: BinarySessionPort["recordEvidence"] | undefined,
sources: readonly Evidence[],
): Result<null, EvidenceIntegrityError | EvidenceLimitError> => {
for (const source of sources) {
const recorded = recordEvidence?.(source);
if (recorded !== undefined && !recorded.ok) return recorded;
}
return ok(null);
};