PR-002: Persistence Adapters — in-memory, zero external dependencies
- Repository interfaces defined by domain (@landvex/domain) - 6 in-memory adapters: Session, Mission, DecisionCase, Artifact, EventStore, UnitOfWork - 9 tests verifying adapter contracts - Domain unchanged — infrastructure depends on domain, never reverse - ADR-006: In-Memory Adapters for Testing Definition of Done met: - All adapters compile against domain interfaces - Unit tests pass (9/9) - No PostgreSQL, S3, Express, AI in this PR - Ready for PR-003: PostgreSQL adapters
This commit is contained in:
@@ -56,3 +56,17 @@ export enum DecisionStatus {
|
||||
APPROVED = 'approved',
|
||||
REJECTED = 'rejected'
|
||||
}
|
||||
|
||||
export enum Priority {
|
||||
LOW = 'low',
|
||||
MEDIUM = 'medium',
|
||||
HIGH = 'high',
|
||||
CRITICAL = 'critical'
|
||||
}
|
||||
|
||||
export enum ConfidenceLevel {
|
||||
LOW = 'low',
|
||||
MEDIUM = 'medium',
|
||||
HIGH = 'high',
|
||||
CERTAIN = 'certain'
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export type FindingId = string & { readonly __brand: 'FindingId' };
|
||||
export type ReviewId = string & { readonly __brand: 'ReviewId' };
|
||||
export type ActionId = string & { readonly __brand: 'ActionId' };
|
||||
export type OutcomeId = string & { readonly __brand: 'OutcomeId' };
|
||||
export type DecisionCaseId = string & { readonly __brand: 'DecisionCaseId' };
|
||||
|
||||
/**
|
||||
* ID factory functions
|
||||
@@ -59,5 +60,9 @@ export const IdFactory = {
|
||||
|
||||
outcome: (sequence: number): OutcomeId => {
|
||||
return `outcome_${sequence.toString().padStart(6, '0')}` as OutcomeId;
|
||||
},
|
||||
|
||||
decisionCase: (sequence: number): DecisionCaseId => {
|
||||
return `decision_${sequence.toString().padStart(6, '0')}` as DecisionCaseId;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,7 +87,7 @@ export interface ValidationResult {
|
||||
}
|
||||
|
||||
export type Severity = 'low' | 'medium' | 'high' | 'critical';
|
||||
export type Priority = 'low' | 'medium' | 'high' | 'urgent';
|
||||
export type PriorityValue = 'low' | 'medium' | 'high' | 'urgent';
|
||||
|
||||
export const Severity = {
|
||||
LOW: 'low' as Severity,
|
||||
@@ -97,8 +97,8 @@ export const Severity = {
|
||||
};
|
||||
|
||||
export const Priority = {
|
||||
LOW: 'low' as Priority,
|
||||
MEDIUM: 'medium' as Priority,
|
||||
HIGH: 'high' as Priority,
|
||||
URGENT: 'urgent' as Priority
|
||||
LOW: 'low' as PriorityValue,
|
||||
MEDIUM: 'medium' as PriorityValue,
|
||||
HIGH: 'high' as PriorityValue,
|
||||
URGENT: 'urgent' as PriorityValue
|
||||
};
|
||||
|
||||
@@ -10,13 +10,18 @@
|
||||
* - Immutable once created — revisions create new versions
|
||||
*/
|
||||
|
||||
import { MissionId, ObservationId, EvidenceId, FindingId, DecisionId, ReviewId } from '../common/ids';
|
||||
import { MissionId, ObservationId, EvidenceId, FindingId, DecisionId, ReviewId, DecisionCaseId } from '../common/ids';
|
||||
import { DecisionStatus } from '../common/enums';
|
||||
import { InvariantViolationError } from '../common/errors';
|
||||
|
||||
export interface DecisionCase {
|
||||
readonly id: string;
|
||||
readonly id: DecisionCaseId;
|
||||
readonly missionId: MissionId;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly priority: string;
|
||||
readonly confidence: string;
|
||||
readonly recommendedAction: string;
|
||||
readonly observationIds: ObservationId[];
|
||||
readonly evidenceIds: EvidenceId[];
|
||||
readonly findingId: FindingId;
|
||||
@@ -28,29 +33,48 @@ export interface DecisionCase {
|
||||
}
|
||||
|
||||
export interface CreateDecisionCaseParams {
|
||||
readonly id: string;
|
||||
readonly id: DecisionCaseId;
|
||||
readonly missionId: MissionId;
|
||||
readonly observationIds: ObservationId[];
|
||||
readonly evidenceIds: EvidenceId[];
|
||||
readonly findingId: FindingId;
|
||||
readonly decisionId: DecisionId;
|
||||
readonly reviewId: ReviewId;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly priority: string;
|
||||
readonly confidence: string;
|
||||
readonly recommendedAction: string;
|
||||
readonly observationIds?: ObservationId[];
|
||||
readonly evidenceIds?: EvidenceId[];
|
||||
readonly findingId?: FindingId;
|
||||
readonly decisionId?: DecisionId;
|
||||
readonly reviewId?: ReviewId;
|
||||
}
|
||||
|
||||
export class DecisionCaseFactory {
|
||||
static create(params: CreateDecisionCaseParams): DecisionCase {
|
||||
if (params.observationIds.length === 0) {
|
||||
const observationIds = params.observationIds ?? [];
|
||||
const evidenceIds = params.evidenceIds ?? [];
|
||||
|
||||
if (observationIds.length === 0) {
|
||||
throw new InvariantViolationError('DecisionCase must have at least one observation');
|
||||
}
|
||||
if (params.evidenceIds.length === 0) {
|
||||
if (evidenceIds.length === 0) {
|
||||
throw new InvariantViolationError('DecisionCase must have at least one evidence');
|
||||
}
|
||||
if (!params.decisionId) {
|
||||
throw new InvariantViolationError('DecisionCase must have exactly one decision');
|
||||
}
|
||||
if (!params.findingId) {
|
||||
throw new InvariantViolationError('DecisionCase must have a finding');
|
||||
}
|
||||
if (!params.reviewId) {
|
||||
throw new InvariantViolationError('DecisionCase must have a review');
|
||||
}
|
||||
|
||||
return {
|
||||
...params,
|
||||
observationIds,
|
||||
evidenceIds,
|
||||
findingId: params.findingId!,
|
||||
decisionId: params.decisionId!,
|
||||
reviewId: params.reviewId!,
|
||||
status: DecisionStatus.PENDING,
|
||||
version: 1,
|
||||
createdAt: new Date()
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface DomainEvent {
|
||||
readonly type: string;
|
||||
readonly timestamp: Date;
|
||||
readonly aggregateId: string;
|
||||
readonly occurredAt: Date;
|
||||
}
|
||||
|
||||
// Session events
|
||||
|
||||
@@ -12,8 +12,38 @@
|
||||
*/
|
||||
|
||||
// Common
|
||||
export * from './common/ids';
|
||||
export * from './common/value-objects';
|
||||
export {
|
||||
SessionId,
|
||||
MissionId,
|
||||
ArtifactId,
|
||||
ObservationId,
|
||||
DecisionId,
|
||||
EvidenceId,
|
||||
FindingId,
|
||||
ReviewId,
|
||||
ActionId,
|
||||
OutcomeId,
|
||||
DecisionCaseId,
|
||||
IdFactory,
|
||||
} from './common/ids';
|
||||
export {
|
||||
GeoLocation,
|
||||
BoundingBox,
|
||||
Confidence,
|
||||
Hash,
|
||||
StorageUri,
|
||||
Version,
|
||||
TimeRange,
|
||||
DeviceInfo,
|
||||
ExifData,
|
||||
AssetMetadata,
|
||||
BusinessImpact,
|
||||
EvidenceContext,
|
||||
QualityIssue,
|
||||
ValidationResult,
|
||||
Severity,
|
||||
PriorityValue,
|
||||
} from './common/value-objects';
|
||||
export * from './common/enums';
|
||||
export * from './common/errors';
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface Mission {
|
||||
readonly device: DeviceInfo;
|
||||
readonly artifactIds: ArtifactId[];
|
||||
readonly observationIds: ObservationId[];
|
||||
readonly sequenceNumber: number;
|
||||
readonly createdAt: Date;
|
||||
readonly updatedAt: Date;
|
||||
}
|
||||
@@ -31,6 +32,7 @@ export interface CreateMissionParams {
|
||||
readonly sessionId: SessionId;
|
||||
readonly location: GeoLocation;
|
||||
readonly device: DeviceInfo;
|
||||
readonly sequenceNumber?: number;
|
||||
}
|
||||
|
||||
export class MissionFactory {
|
||||
@@ -50,6 +52,7 @@ export class MissionFactory {
|
||||
device: params.device,
|
||||
artifactIds: [],
|
||||
observationIds: [],
|
||||
sequenceNumber: params.sequenceNumber ?? 1,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user