CRUMB a card from devarno-cloud

Requirement & Relationship Schema

traceo intermediate 6 min read

ELI5

A requirement is a sticky note pinned on a five-shelf cabinet (L1-StrategicL4-Verification). Relationships are coloured strings tied between sticky notes — every string has a known opposite colour, so pulling on one end automatically labels the other. Traceo stores both ends so a query from either side is a single index lookup.

Technical Deep Dive

Core Tables (migration 001_initial_schema.sql)

TablePurpose
tenantsTop-level billing/isolation boundary
workspacesPer-tenant project scope (RLS pivot)
usersMembers, with role for RBAC
requirementsThe atomic unit; stores type, classification, priority, status
relationshipsEdges between requirements
requirement_versionsAppend-only history per requirement
ingestion_jobs, job_issuesEngine state
audit_logsAppend-only audit trail

Requirement Enums

EnumValues
requirement_typebusiness, functional, non_functional, system, implementation, verification
requirement_classificationL1-Strategic, L2-Functional, L2-Performance, L3-System, L4-Implementation, L4-Verification
requirement_priorityP0-critical, P1-high, P2-medium, P3-low
requirement_statusdraft → candidate → approved → active → implemented → verified, plus deprecated

Relationship Types & Reverses

relationship_type is a single enum holding 15 values. The MCP service services/traceability.py keeps a static reverse map so that adding A implements B also writes B implemented_by A:

ForwardReverse
implementsimplemented_by
satisfiesvalidates
derives_fromderived_by
verifiesverified_by
depends_onaffected_by
affectsaffected_by
supportsrealized_by

Asymmetric values (decomposes_into, conflicts_with, relates_to, constrains, constrained_by) have no reverse mapping and are stored only in the direction the caller supplied.

Class Diagram

classDiagram
class Requirement {
+UUID id
+UUID workspace_id
+text requirement_id
+requirement_type type
+requirement_classification classification
+requirement_priority priority
+requirement_status status
+vector(1536) embedding
}
class Relationship {
+UUID id
+UUID source_id
+UUID target_id
+relationship_type type
}
class RequirementVersion {
+UUID id
+UUID requirement_id
+int version
+jsonb snapshot
}
Requirement "1" --> "*" Relationship : source
Requirement "1" --> "*" Relationship : target
Requirement "1" --> "*" RequirementVersion

Key Terms

  • Reverse map_REVERSE_RELATIONSHIPS in services/traceability.py; converts a forward type to its symmetric counterpart.
  • Classification → fixed 5-layer hierarchy used for traceability matrix rendering.
  • Workspace → the RLS pivot column carried on every domain row.

Q&A

Q: If I add A satisfies B, what edge does Traceo also write? A: B validates A — the reverse of satisfies is validates per the static map.

Q: Which relationship types have no reverse? A: decomposes_into, conflicts_with, relates_to, constrains, constrained_by — they fall through _reverse_relationship_type returning None, so only the supplied direction is persisted.

Q: Where is per-requirement history stored? A: requirement_versions — an append-only table with a JSONB snapshot per version, distinct from audit_logs.

Examples

Calling add_link(source="REQ-1", target="REQ-2", type="implements") writes two rows in relationships: (REQ-1, REQ-2, implements) and (REQ-2, REQ-1, implemented_by). A subsequent trace_requirement("REQ-2") finds the inbound implemented_by edge without needing a reverse-direction index.

neighbors on the map