- Added Three Rules to README:
1. Domain knows nothing about AI (no AIObservation, YOLODetection, etc.)
2. Everything is an Artifact (common contract for all produced objects)
3. All decisions are reproducible (answer: which observations, evidence,
model, version, rules, reviewer, when, approved version)
- Renamed Human Intelligence → Experience Intelligence
- Broader scope: citizen surveys, customer satisfaction, field technician
feedback, contractor experience, service desk data
- More future-proof than 'Human Intelligence'
- Added Capability Tags for developers:
- Observation: [Detection, Vision, GPS, Image]
- Decision: [Decision, Recommendation, Business]
- Artifact: [Storage, Versioning, Lineage]
- Makes dependencies clear as platform grows
- Updated Domain Invariants:
- DecisionCase: must have at least one Review before Approved
- Review: must belong to exactly one Decision, must have reviewer
- Added Merge Criteria:
- All stakeholders (developer, AI engineer, product owner, domain expert)
can read model and understand same terms
- No AI objects, all Artifacts, reproducible decisions
Rationale: Lock domain language before implementation. Shared vocabulary
for code, docs, APIs, tests, and product discussions.
15 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.
Three Rules
Rule 1: Domain knows nothing about AI
No AI-specific objects in domain:
- ❌
AIObservation - ❌
YOLODetection - ❌
GeminiResult - ❌
ClaudeAnalysis
Domain only knows:
- ✅
Observation - ✅
Evidence - ✅
Finding - ✅
Decision - ✅
Review - ✅
Outcome
If we switch from YOLO to a custom model in five years, the domain does not change.
Rule 2: Everything is an Artifact
Artifact is a common contract for everything produced:
Artifact
├── Image
├── Video
├── Dataset
├── Annotation
├── Model
├── Evaluation
├── DecisionCase
├── Report
└── ExperienceInsight
All artifacts share:
idversionhashlineagecreatedAtcreatedBystorageUri
This makes the entire platform consistent.
Rule 3: All decisions are reproducible
Every Decision Case must answer:
- Which observations were used?
- Which evidence was used?
- Which model?
- Which model version?
- Which rules?
- Who reviewed?
- When?
- Which version was approved?
If any question cannot be answered, the Decision Case is incomplete.
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
- Must have at least one Review before Approved
- 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
Review
- Must belong to exactly one Decision
- Must have a reviewer
- Must have a status (Assigned, In Review, Approved, Rejected, Needs More Evidence)
- Approved requires at least one completed review
Capability Tags
For developers — not users. Makes dependencies clear as platform grows.
Observation
├── Capabilities: [Detection, Vision, GPS, Image]
Decision
├── Capabilities: [Decision, Recommendation, Business]
Artifact
├── Capabilities: [Storage, Versioning, Lineage]
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;
reviewIds: 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 |
| Human Intelligence | Separate module, not core domain |
Future Module: Experience Intelligence (Not in Epic-001)
Positioning: External Intelligence Module, not part of core domain.
Why separate: Stakeholder feedback is not an observation from reality like a road crack. It is context that can influence priorities and decisions.
Architecture:
Reality Intelligence
│
├── Infrastructure Intelligence ← Core (Epic-001)
│ Roads, Buildings, Parks, Signs
│
├── Operational Intelligence ← Future
│ SLA, Tickets, Costs, Times
│
├── Experience Intelligence ← Future module
│ Citizen surveys
│ Customer satisfaction
│ Field technician feedback
│ Contractor experience
│ Service desk data
│ NPS, Complaints, Feedback
│
└── External Intelligence ← Future
│ Weather, Traffic, Demographics
Experience Insight Artifact:
Experience Insight
├── Source: "Kommunenkät 2026"
├── Geography: [Stockholm, Nacka]
├── Period: "Q2 2026"
├── Stakeholders: [Citizens, Field Technicians, Contractors]
├── Metrics:
│ ├── CSAT: 4.2
│ ├── NPS: 62
│ └── Complaint Index: 0.34
└── Confidence: 0.83
Usage: Linked to Area or Decision Case, but never mixed with raw field observations.
Decision Case with Experience Intelligence:
Observation: "Crack on main street"
Evidence: [Image, Video, History, GIS]
Experience Intelligence: "Many complaints last 30 days, low satisfaction index"
Decision: "Prioritize repair within 14 days"
Business Impact: "Reduced risk, lower future cost, improved citizen satisfaction"
When: After Epic-001 is stable and 20+ Decision Cases exist.
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
Merge Criteria:
- Developer can read model and understand domain language
- AI engineer can read model and understand domain language
- Product owner can read model and understand domain language
- Domain expert can read model and understand domain language
- All use same terms for
Observation,Evidence,Decision,Outcome - No AI-specific objects in domain
- All objects are Artifacts
- All decisions are reproducible
- Domain invariants documented
- Capability tags defined