Skip to content

Data Model

The Tech Strategy Tool’s domain model is a hierarchy of strategy entities managed through an event-sourced processor. This page describes the entity relationships, storage model, and how the in-memory document relates to persistent storage.

Diagram

Solid lines in the diagram indicate ownership — the parent entity’s deletion cascades to the child. The Objective-to-Principle relationship is a reference — deleting a Principle removes the association but does not delete the Objective.

RelationshipTypeCascade behavior
Strategy -> TeamOwnership (ordered)Deleting strategy removes all teams
Team -> PrincipleOwnership (ordered)Deleting team removes all principles
Team -> GroupOwnership (ordered)Deleting team removes all groups
Team -> ObjectiveOwnership (ordered)Deleting team removes all objectives
Objective -> InitiativeOwnership (ordered)Deleting objective removes all initiatives
Objective -> PrincipleReference (many-to-many)Deleting principle removes association only
Objective -> GroupReference (optional)Deleting group unassigns objectives (they become ungrouped)

The root document. Contains an ordered list of teams. There is exactly one Strategy instance in the system, held in memory by the event processor.

A team represents an engineering group with its own strategy space. Teams are created and managed exclusively in the Admin Site.

FieldTypeDescription
IdGuidUnique identifier
NamestringDisplay name
ColorstringHex color code (e.g., #3498db) — validated on create and update
PrinciplesList<Principle>Ordered list of principles
ObjectivesList<Objective>Ordered list of objectives
GroupsList<Group>Ordered list of groups for organizing objectives

A grouping mechanism for objectives within a team. Groups provide visual organization in the Objectives view.

FieldTypeDescription
IdGuidUnique identifier
NamestringDisplay name
DescriptionstringOptional description

Deleting a group unassigns its objectives (they become ungrouped and display under a default heading) but does not delete them.

A guiding belief or standard that a team follows. Principles represent the “why” behind technology decisions.

FieldTypeDescription
IdGuidUnique identifier
NamestringPrinciple name (supports *highlight* syntax — highlighted text renders in the team color)
DescriptionstringDetailed description

Cross-team sharing: When assigned to an Objective in a different team, an independent copy is created in the Objective’s team. The copy is a separate entity — there is no live link to the source. Each team can edit its copy independently.

Cascade on delete: Deleting a principle removes it from the PrincipleIds list of all objectives that reference it in the same team.

A measurable goal aligned to one or more principles. Objectives track progress through their initiatives.

FieldTypeDescription
IdGuidUnique identifier
NamestringObjective name
GroupIdGuid?Optional group assignment
PrincipleIdsList<Guid>Linked principles (many-to-many by reference)
InitiativesList<Initiative>Ordered list of initiatives

Total progress: Computed as the arithmetic mean of all child initiative progress values, rounded to the nearest integer. Returns 0% if the objective has no initiatives.

Principle linking: A principle can be associated with an objective only once — duplicate assignment is rejected.

A concrete project or work item that drives progress on an objective.

FieldTypeDescription
IdGuidUnique identifier
NamestringInitiative name (plain text, no highlight syntax)
ProgressintCompletion percentage (0-100)
JiraKeystring?Optional Jira issue key

Deleting an initiative immediately recalculates the parent objective’s total progress.

All entity IDs are globally unique GUIDs. The processor locates entities by ID alone without needing to know the parent hierarchy. This simplifies lookups for events like update_name where only the target entity’s ID is needed.

All collections use List<T>, which preserves insertion order and supports insert-at-index operations:

  • Principles within their Team
  • Groups within their Team
  • Objectives within their Group (or the ungrouped section)
  • Initiatives within their Objective

Ordering is modified via reorder events (reorder_objective, reorder_initiative, reorder_group) that specify a target index. The UI exposes this through drag-and-drop.

The domain model exists in two forms:

The EventProcessor holds the current Strategy document in memory. This is the authoritative state for all reads. The document is a plain C# object graph with mutable properties and List<T> collections.

The database stores the building blocks for reconstructing the in-memory document:

TablePurpose
eventsAppend-only event log (JSONB data column)
checkpointsPeriodic document snapshots (JSONB document_json column)
history_associationsMaps events to all entities they affect (for entity history queries)
usersUser accounts with password hashes
sessionsActive login sessions
TableColumnTypePurpose
eventssequence_numberUniqueEvent ordering
eventstarget_idNon-uniqueEntity history queries
checkpointssequence_numberUniqueCheckpoint ordering
history_associationsentity_idNon-uniqueEntity history queries
history_associationsevent_sequenceNon-uniqueEvent-based lookups and restore cleanup
usersusernameUniqueLogin lookups
sessionsuser_idNon-uniquePer-user session queries

Event data is stored as a JSON dictionary in the data JSONB column. Each event type has its own set of expected keys:

// create_entity event data (Team — targetType: "Team" on the envelope)
{ "id": "a1b2c3d4-...", "name": "Platform Team", "color": "#3498db" }
// update_name event data (targetType: "Principle" on the envelope)
{ "name": "API-First Design" }

Checkpoints serialize the entire Strategy document as JSON using System.Text.Json. The JSON is stored in a JSONB column, which PostgreSQL normalizes (whitespace is stripped).

  • Entities — Detailed entity documentation with all fields and behaviors
  • Event Processing — How events modify the domain model
  • Event Types — Complete catalog of all event types