Skip to main content

Dependencies

The Dependency type provides relationship management between elements, supporting blocking relationships for task orchestration, associative links for knowledge graphs, and attribution tracking.

Dependency Interface

interface Dependency {
  readonly blockedId: ElementId;    // Element that is waiting/blocked
  readonly blockerId: ElementId;    // Element doing the blocking
  readonly type: DependencyType;    // Category of relationship
  readonly createdAt: Timestamp;    // When dependency was created
  readonly createdBy: EntityId;     // Who created the dependency
  metadata: Record<string, unknown>; // Type-specific data
}
The composite key is (blockedId, blockerId, type), allowing multiple dependency types between the same elements.
Blocking direction: In blocks dependencies, blockedId is the task that is BLOCKED BY blockerId. The blocker must complete first.

Dependency Types

Dependencies are categorized into four groups:

Blocking Dependencies

Affect work readiness - tasks cannot proceed until blockers resolve:
const BlockingDependencyType = {
  BLOCKS: 'blocks',              // Blocked element waits for blocker to close
  PARENT_CHILD: 'parent-child',  // Hierarchical containment with transitive blocking
  AWAITS: 'awaits',              // External gate dependency
} as const;
Task A is blocked by Task B:
// Task A waits for Task B to close
{ blockedId: taskA.id, blockerId: taskB.id, type: 'blocks' }
Direction reminder: sf dependency add --type=blocks A B means A is blocked BY B

Associative Dependencies

Non-blocking knowledge graph connections:
const AssociativeDependencyType = {
  RELATES_TO: 'relates-to',   // Semantic bidirectional link
  REFERENCES: 'references',   // Citation (unidirectional)
  SUPERSEDES: 'supersedes',   // Version chain
  DUPLICATES: 'duplicates',   // Deduplication marker
  CAUSED_BY: 'caused-by',     // Audit trail causation
  VALIDATES: 'validates',     // Test verification link
  MENTIONS: 'mentions',       // @mention link from message to entity
} as const;
relates-to is bidirectional by design. Query both directions using getDependencies() and getDependents().

Attribution Dependencies

Link elements to entities:
const AttributionDependencyType = {
  AUTHORED_BY: 'authored-by',   // Creator attribution
  ASSIGNED_TO: 'assigned-to',   // Responsibility assignment
  APPROVED_BY: 'approved-by',   // Sign-off approval
} as const;

Threading Dependencies

Message conversation threading:
const ThreadingDependencyType = {
  REPLIES_TO: 'replies-to',  // Thread parent reference
} as const;

Gate Types

For awaits dependencies, the metadata field contains gate-specific information:

Timer Gate

Time-based gate that opens at a specific timestamp:
interface TimerGateMetadata {
  gateType: 'timer';
  waitUntil: Timestamp;  // ISO 8601 timestamp when gate opens
}

Approval Gate

Requires approval from specific entities:
interface ApprovalGateMetadata {
  gateType: 'approval';
  requiredApprovers: EntityId[];  // Entities that must approve
  approvalCount?: number;         // Number needed (defaults to all)
  currentApprovers?: EntityId[];  // Current approvers (tracked)
}

External Gate

External system confirmation:
interface ExternalGateMetadata {
  gateType: 'external';
  externalSystem: string;   // External system identifier
  externalId: string;       // External reference ID
  satisfied?: boolean;      // Whether gate is satisfied
  satisfiedAt?: Timestamp;  // When gate was satisfied
  satisfiedBy?: EntityId;   // Actor who satisfied
}

Webhook Gate

Webhook callback:
interface WebhookGateMetadata {
  gateType: 'webhook';
  webhookUrl?: string;
  callbackId?: string;
  satisfied?: boolean;
  satisfiedAt?: Timestamp;
  satisfiedBy?: EntityId;
}

Test Validation

For validates dependencies, the metadata contains test results:
interface ValidatesMetadata {
  testType: TestType | string;  // unit, integration, manual, e2e, property, or custom
  result: 'pass' | 'fail';
  details?: string;             // Test output or notes
}

const TestType = {
  UNIT: 'unit',
  INTEGRATION: 'integration',
  MANUAL: 'manual',
  E2E: 'e2e',
  PROPERTY: 'property',
} as const;

Creating Dependencies

Generic Dependency

interface CreateDependencyInput {
  blockedId: ElementId;
  blockerId: ElementId;
  type: DependencyType;
  createdBy: EntityId;
  metadata?: Record<string, unknown>;
}

// Create dependency
createDependency(input: CreateDependencyInput): Dependency

Awaits Dependency

interface CreateAwaitsDependencyInput {
  blockedId: ElementId;
  blockerId: ElementId;
  createdBy: EntityId;
  awaitsMetadata: AwaitsMetadata;
}

// Create awaits dependency
createAwaitsDependency(input: CreateAwaitsDependencyInput): Dependency

Validates Dependency

interface CreateValidatesDependencyInput {
  blockedId: ElementId;
  blockerId: ElementId;
  createdBy: EntityId;
  validatesMetadata: ValidatesMetadata;
}

// Create validates dependency
createValidatesDependency(input: CreateValidatesDependencyInput): Dependency

Type Guards and Validation

Type Guards

// Check if value is valid Dependency
isDependency(value: unknown): value is Dependency

// Check dependency type category
isBlockingDependencyType(value: unknown): value is BlockingDependencyType
isAssociativeDependencyType(value: unknown): value is AssociativeDependencyType
isAttributionDependencyType(value: unknown): value is AttributionDependencyType
isThreadingDependencyType(value: unknown): value is ThreadingDependencyType

// Check specific dependency
isBlockingDependency(dependency: Dependency): boolean
isAssociativeDependency(dependency: Dependency): boolean
isMentionsDependency(dependency: Dependency): boolean

Validation

// Validate dependency
validateDependency(value: unknown): Dependency

// Validate dependency type
validateDependencyType(value: unknown): DependencyType

// Validate metadata
validateAwaitsMetadata(value: unknown): AwaitsMetadata
validateValidatesMetadata(value: unknown): ValidatesMetadata

Filtering Dependencies

// Filter by type
filterByType<T extends Dependency>(dependencies: T[], type: DependencyType): T[]

// Filter by category
filterBlocking<T extends Dependency>(dependencies: T[]): T[]
filterAssociative<T extends Dependency>(dependencies: T[]): T[]

// Filter by element
filterByBlocked<T extends Dependency>(dependencies: T[], blockedId: ElementId): T[]
filterByBlocker<T extends Dependency>(dependencies: T[], blockerId: ElementId): T[]

Metadata Extraction

// Get awaits metadata from dependency
getAwaitsMetadata(dependency: Dependency): AwaitsMetadata | null

// Get validates metadata from dependency
getValidatesMetadata(dependency: Dependency): ValidatesMetadata | null

Display Utilities

// Get display name for dependency type
getDependencyTypeDisplayName(type: DependencyType): string
// Returns: "Blocks", "Parent-Child", "Awaits", etc.

// Get display name for gate type
getGateTypeDisplayName(gateType: GateType): string
// Returns: "Timer", "Approval", "External System", "Webhook"

// Get human-readable description
describeDependency(dependency: Dependency): string
// Returns: "{blockedId} blocks {blockerId}"

Relates-To Helpers

For bidirectional relates-to dependencies:
// Normalize relates-to IDs (smaller ID always as blockedId)
normalizeRelatesToDependency(
  blockedId: ElementId,
  blockerId: ElementId
): { blockedId: ElementId; blockerId: ElementId }

// Check if two elements are related (in either direction)
areRelated(
  dependencies: Dependency[],
  elementA: ElementId,
  elementB: ElementId
): boolean

Cycle Detection

Only blocking dependencies participate in cycle detection:
// Check if type participates in cycle detection
participatesInCycleDetection(type: DependencyType): boolean

// Note: relates-to is excluded (bidirectional by design)
The createDependency() function does NOT check for cycles. Use DependencyService.detectCycle() from @stoneforge/quarry for cycle detection.

Validation Constants

const VALID_DEPENDENCY_TYPES = [
  'blocks', 'parent-child', 'awaits',
  'relates-to', 'references', 'supersedes', 'duplicates', 'caused-by', 'validates', 'mentions',
  'authored-by', 'assigned-to', 'approved-by',
  'replies-to'
];

const BLOCKING_DEPENDENCY_TYPES = ['blocks', 'parent-child', 'awaits'];
const ASSOCIATIVE_DEPENDENCY_TYPES = ['relates-to', 'references', 'supersedes', 'duplicates', 'caused-by', 'validates', 'mentions'];
const ATTRIBUTION_DEPENDENCY_TYPES = ['authored-by', 'assigned-to', 'approved-by'];
const THREADING_DEPENDENCY_TYPES = ['replies-to'];

Best Practices

Understand Direction

In blocks dependencies, blockedId is blocked BY blockerId. The blocker completes first.

No Cycle Detection

createDependency() doesn’t check cycles. Use DependencyService.detectCycle() separately.

Bidirectional Queries

For relates-to, query both directions using getDependencies() and getDependents().

Gate Metadata

Always validate awaitsMetadata when creating gate dependencies.

Build docs developers (and LLMs) love