Files
boc/docs/design/PR-001-DOMAIN-MODEL.md
T
Bernt a08e897f96 docs: PR-001 Domain Model — Core language for LandveX Intelligence Lab
- Package: @landvex/domain
- Zero dependencies on Express, PostgreSQL, S3, or AI frameworks
- Four package structure: domain, contracts, shared, infrastructure
- Domain never depends on infrastructure

- Aggregate Roots: FieldSession, Mission, DecisionCase
- Entities: MissionAsset, Artifact, Observation, Evidence, Finding,
  Decision, Review, Action, Outcome
- Value Objects: MissionId, ArtifactId, SessionId, ObservationId,
  DecisionId, GeoLocation, GpsAccuracy, Confidence, Severity, Priority,
  Hash, StorageUri, Version

- Enums: ArtifactType, ReviewStatus, DecisionVerb, MissionStatus, SessionStatus
- Event Contracts: FieldSessionCreated, MissionCreated, MissionImported,
  ArtifactUploaded, ArtifactValidated, ObservationCreated, EvidenceCreated,
  FindingCreated, DecisionCreated, DecisionReviewed, DecisionApproved

- Domain Invariants:
  - FieldSession: must have location, date, can have zero missions
  - Mission: must belong to one Session, must have at least one Artifact
  - DecisionCase: must have at least one Observation, one Evidence,
    exactly one Decision, cannot be Approved without Review
  - Artifact: must have unique hash, storage URI, versioned

- TypeScript interfaces for all domain objects
- Branded ID types for type safety
- Strict rule: No new feature may introduce new domain concepts without
  approved change to domain model

- README: 'This package describes LandveX domain model. Contains no
  dependencies to database, HTTP, cloud storage, or AI frameworks.'

- Next: PR-002 Persistence Adapter (Repository Interface → PostgreSQL/S3/Event Store)

Rationale: Lock the language before writing infrastructure code.
Shared vocabulary for code, docs, APIs, tests, and product discussions.
2026-07-02 13:49:19 +00:00

11 KiB

PR-001: Domain Model

Package: @landvex/domain

PR 001
Scope Domain language only — no database, no HTTP, no buckets, no AI
Rule This package has zero dependencies on Express, PostgreSQL, S3, or AI frameworks

README

This package describes the LandveX domain model. It contains no dependencies to database, HTTP, cloud storage, or AI frameworks. It defines only the language, objects, and rules that the rest of the platform builds upon.


Package Structure

packages/
├── domain/
│   ├── session/
│   ├── mission/
│   ├── artifact/
│   ├── observation/
│   ├── evidence/
│   ├── finding/
│   ├── decision/
│   ├── review/
│   ├── event/
│   └── common/
│
├── contracts/
│   ├── api/
│   ├── events/
│   └── dto/
│
├── shared/
│
└── infrastructure/

Domain never depends on infrastructure.


Domain Objects

Aggregate Roots

Object Type Description
FieldSession Aggregate Root Organizes field work
Mission Aggregate Root Single data collection task
DecisionCase Aggregate Root Complete decision chain

Entities

Object Description
MissionAsset Photo, video, or sensor data
Artifact Any produced artifact (versioned, immutable)
Observation Recorded fact from reality
Evidence Linked observations with context
Finding Pattern or conclusion
Decision Recommended action
Review Human quality assessment
Action Executed task
Outcome Measured result

Value Objects

Object Description Example
MissionId Unique mission identifier mission_20260814_000123
ArtifactId Unique artifact identifier artifact_000456
SessionId Unique session identifier session_20260814_000001
ObservationId Unique observation identifier obs_000981
DecisionId Unique decision identifier decision_000044
GeoLocation Latitude and longitude { lat: 59.2371, lng: 18.1456 }
GpsAccuracy GPS accuracy in meters 4.5
Confidence Model confidence 0-1 0.87
Severity Issue severity low, medium, high, critical
Priority Action priority low, medium, high, urgent
Hash SHA-256 checksum sha256:a3f7...
StorageUri Storage location s3://landvex-raw/...
Version Semantic version 1, 2, 3

Enums

ArtifactType

enum ArtifactType {
  IMAGE = 'image',
  VIDEO = 'video',
  AUDIO = 'audio',
  DATASET = 'dataset',
  ANNOTATION = 'annotation',
  DECISION_CASE = 'decision_case',
  MODEL = 'model',
  EVALUATION = 'evaluation',
  REPLAY = 'replay'
}

ReviewStatus

enum ReviewStatus {
  ASSIGNED = 'assigned',
  IN_REVIEW = 'in_review',
  APPROVED = 'approved',
  REJECTED = 'rejected',
  NEEDS_MORE_EVIDENCE = 'needs_more_evidence'
}

DecisionVerb

enum DecisionVerb {
  INSPECT = 'inspect',
  REPAIR = 'repair',
  MONITOR = 'monitor',
  WAIT = 'wait',
  COLLECT = 'collect',
  ESCALATE = 'escalate',
  IGNORE = 'ignore',
  PRIORITIZE = 'prioritize'
}

MissionStatus

enum MissionStatus {
  CREATED = 'created',
  UPLOADING = 'uploading',
  PROCESSING = 'processing',
  COMPLETED = 'completed',
  FAILED = 'failed'
}

SessionStatus

enum SessionStatus {
  PLANNED = 'planned',
  ACTIVE = 'active',
  COMPLETED = 'completed'
}

Event Contracts

// Session events
interface FieldSessionCreated {
  type: 'FieldSessionCreated';
  sessionId: SessionId;
  location: GeoLocation;
  date: Date;
  timestamp: Date;
}

// Mission events
interface MissionCreated {
  type: 'MissionCreated';
  missionId: MissionId;
  sessionId: SessionId;
  location: GeoLocation;
  timestamp: Date;
}

interface MissionImported {
  type: 'MissionImported';
  missionId: MissionId;
  assetCount: number;
  timestamp: Date;
}

// Artifact events
interface ArtifactUploaded {
  type: 'ArtifactUploaded';
  artifactId: ArtifactId;
  missionId: MissionId;
  type: ArtifactType;
  storageUri: StorageUri;
  hash: Hash;
  timestamp: Date;
}

interface ArtifactValidated {
  type: 'ArtifactValidated';
  artifactId: ArtifactId;
  validationResult: ValidationResult;
  timestamp: Date;
}

// Observation events
interface ObservationCreated {
  type: 'ObservationCreated';
  observationId: ObservationId;
  missionId: MissionId;
  artifactId: ArtifactId;
  description: string;
  timestamp: Date;
}

// Evidence events
interface EvidenceCreated {
  type: 'EvidenceCreated';
  evidenceId: string;
  observationIds: ObservationId[];
  description: string;
  timestamp: Date;
}

// Finding events
interface FindingCreated {
  type: 'FindingCreated';
  findingId: string;
  evidenceId: string;
  description: string;
  severity: Severity;
  timestamp: Date;
}

// Decision events
interface DecisionCreated {
  type: 'DecisionCreated';
  decisionId: DecisionId;
  findingId: string;
  verb: DecisionVerb;
  description: string;
  confidence: Confidence;
  timestamp: Date;
}

interface DecisionReviewed {
  type: 'DecisionReviewed';
  decisionId: DecisionId;
  reviewId: string;
  status: ReviewStatus;
  reviewer: string;
  timestamp: Date;
}

interface DecisionApproved {
  type: 'DecisionApproved';
  decisionId: DecisionId;
  reviewId: string;
  approvedBy: string;
  timestamp: Date;
}

Domain Invariants

FieldSession

  • Must have exactly one location
  • Must have a date
  • Can have zero or more missions
  • Cannot be completed if any mission is still uploading

Mission

  • Must belong to exactly one Session
  • Must have at least one Artifact
  • Cannot be completed before import is finished
  • Status is a projection of event history, not mutable state

DecisionCase

  • Must have at least one Observation
  • Must have at least one Evidence
  • Must have exactly one Decision
  • Cannot be Approved without Review
  • Immutable once created — revisions create new versions

Artifact

  • Must have a unique hash
  • Must have a storage URI
  • Version is incremented on each change
  • Parent reference creates lineage chain

TypeScript Interfaces

// Aggregate Roots
interface FieldSession {
  id: SessionId;
  location: GeoLocation;
  date: Date;
  status: SessionStatus;
  missionIds: MissionId[];
  createdAt: Date;
  updatedAt: Date;
}

interface Mission {
  id: MissionId;
  sessionId: SessionId;
  location: GeoLocation;
  status: MissionStatus;
  device: DeviceInfo;
  artifactIds: ArtifactId[];
  observationIds: ObservationId[];
  createdAt: Date;
  updatedAt: Date;
}

interface DecisionCase {
  id: string;
  missionId: MissionId;
  observationIds: ObservationId[];
  evidenceIds: string[];
  findingId: string;
  decisionId: DecisionId;
  reviewId: string;
  status: 'pending' | 'under_review' | 'approved' | 'rejected';
  version: number;
  createdAt: Date;
}

// Entities
interface MissionAsset {
  id: ArtifactId;
  missionId: MissionId;
  type: ArtifactType;
  storageUri: StorageUri;
  hash: Hash;
  sizeBytes: number;
  mimeType: string;
  metadata: AssetMetadata;
  createdAt: Date;
}

interface Artifact {
  id: ArtifactId;
  type: ArtifactType;
  version: Version;
  hash: Hash;
  createdBy: string;
  storageUri: StorageUri;
  parentId?: ArtifactId;
  lineage: ArtifactId[];
  createdAt: Date;
}

interface Observation {
  id: ObservationId;
  missionId: MissionId;
  artifactId: ArtifactId;
  type: string;
  description: string;
  location: GeoLocation;
  confidence: Confidence;
  boundingBox?: BoundingBox;
  createdAt: Date;
}

interface Evidence {
  id: string;
  observationIds: ObservationId[];
  description: string;
  context: EvidenceContext;
  createdAt: Date;
}

interface Finding {
  id: string;
  evidenceId: string;
  description: string;
  severity: Severity;
  pattern: string;
  confidence: Confidence;
  createdAt: Date;
}

interface Decision {
  id: DecisionId;
  findingId: string;
  verb: DecisionVerb;
  description: string;
  rationale: string;
  confidence: Confidence;
  consequence: string;
  action: string;
  businessImpact: BusinessImpact;
  createdAt: Date;
}

interface Review {
  id: string;
  decisionId: DecisionId;
  status: ReviewStatus;
  reviewer: string;
  comments: string;
  createdAt: Date;
  completedAt?: Date;
}

interface Action {
  id: string;
  decisionId: DecisionId;
  description: string;
  assignedTo: string;
  scheduledDate: Date;
  status: 'pending' | 'in_progress' | 'completed';
  createdAt: Date;
}

interface Outcome {
  id: string;
  actionId: string;
  description: string;
  measuredImpact: BusinessImpact;
  verifiedBy: string;
  createdAt: Date;
}

// Value Objects
interface GeoLocation {
  lat: number;
  lng: number;
  accuracy?: GpsAccuracy;
}

interface BoundingBox {
  x: number;
  y: number;
  width: number;
  height: number;
}

interface AssetMetadata {
  exif: ExifData;
  gps: GeoLocation;
  device: DeviceInfo;
}

interface ExifData {
  device?: string;
  iso?: number;
  exposure?: string;
  focalLength?: string;
  timestamp?: Date;
}

interface DeviceInfo {
  model: string;
  os: string;
  appVersion: string;
}

interface EvidenceContext {
  historicalData?: boolean;
  weatherData?: boolean;
  trafficData?: boolean;
  gisData?: boolean;
}

interface BusinessImpact {
  risk: string;
  cost: string;
  time: string;
  opportunity: string;
}

interface ValidationResult {
  valid: boolean;
  issues: QualityIssue[];
}

interface QualityIssue {
  type: 'blur' | 'duplicate' | 'bad_gps' | 'low_resolution' | 'missing_metadata' | 'wrong_timestamp' | 'occlusion';
  severity: Severity;
  description: string;
}

// ID Types (branded types for type safety)
type MissionId = string & { readonly __brand: 'MissionId' };
type ArtifactId = string & { readonly __brand: 'ArtifactId' };
type SessionId = string & { readonly __brand: 'SessionId' };
type ObservationId = string & { readonly __brand: 'ObservationId' };
type DecisionId = string & { readonly __brand: 'DecisionId' };

Strict Rule

No new feature may introduce new domain concepts without an approved change to the domain model.

The concepts Session, Mission, Artifact, Observation, Evidence, Finding, Decision, Review, and Outcome become the shared language for the entire organization — code, documentation, APIs, tests, and product discussions use the same terms.


Not in PR-001

Item Why Excluded
Repositories Domain should not know about persistence
Services Will come in next PR
Database code Infrastructure concern
HTTP endpoints API concern
Bucket code Infrastructure concern
AI logic Separate concern

Next PR

PR-002: Persistence Adapter

Domain
    ↓
Repository Interface
    ↓
PostgreSQL Adapter
    ↓
S3 Adapter
    ↓
Event Store Adapter

Freedom to change technology without touching core model.


Status

READY FOR IMPLEMENTATION