Phase 1: Edge schema consolidation infrastructure (Decision 004)
Add metadata and thread_id columns to dependencies table to support: - Edge metadata: JSON blob for type-specific data (similarity scores, etc.) - Thread queries: O(1) conversation threading via thread_id Changes: - New migration 020_edge_consolidation.go - Updated Dependency struct with Metadata and ThreadID fields - Added new entity types: authored-by, assigned-to, approved-by - Relaxed DependencyType validation (any non-empty string ≤50 chars) - Added IsWellKnown() and AffectsReadyWork() methods - Updated SQL queries to include new columns - Updated tests for new behavior This enables HOP knowledge graph requirements and Reddit-style threading. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -244,6 +244,12 @@ type Dependency struct {
|
||||
Type DependencyType `json:"type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
// Metadata contains type-specific edge data (JSON blob)
|
||||
// Examples: similarity scores, approval details, skill proficiency
|
||||
Metadata string `json:"metadata,omitempty"`
|
||||
// ThreadID groups conversation edges for efficient thread queries
|
||||
// For replies-to edges, this identifies the conversation root
|
||||
ThreadID string `json:"thread_id,omitempty"`
|
||||
}
|
||||
|
||||
// DependencyCounts holds counts for dependencies and dependents
|
||||
@@ -271,27 +277,51 @@ type DependencyType string
|
||||
|
||||
// Dependency type constants
|
||||
const (
|
||||
DepBlocks DependencyType = "blocks"
|
||||
// Workflow types (affect ready work calculation)
|
||||
DepBlocks DependencyType = "blocks"
|
||||
DepParentChild DependencyType = "parent-child"
|
||||
|
||||
// Association types
|
||||
DepRelated DependencyType = "related"
|
||||
DepParentChild DependencyType = "parent-child"
|
||||
DepDiscoveredFrom DependencyType = "discovered-from"
|
||||
|
||||
// Graph link types (bd-kwro)
|
||||
DepRepliesTo DependencyType = "replies-to" // Conversation threading
|
||||
DepRelatesTo DependencyType = "relates-to" // Loose knowledge graph edges
|
||||
DepDuplicates DependencyType = "duplicates" // Deduplication link
|
||||
DepSupersedes DependencyType = "supersedes" // Version chain link
|
||||
DepRepliesTo DependencyType = "replies-to" // Conversation threading
|
||||
DepRelatesTo DependencyType = "relates-to" // Loose knowledge graph edges
|
||||
DepDuplicates DependencyType = "duplicates" // Deduplication link
|
||||
DepSupersedes DependencyType = "supersedes" // Version chain link
|
||||
|
||||
// Entity types (HOP foundation - Decision 004)
|
||||
DepAuthoredBy DependencyType = "authored-by" // Creator relationship
|
||||
DepAssignedTo DependencyType = "assigned-to" // Assignment relationship
|
||||
DepApprovedBy DependencyType = "approved-by" // Approval relationship
|
||||
)
|
||||
|
||||
// IsValid checks if the dependency type value is valid
|
||||
// IsValid checks if the dependency type value is valid.
|
||||
// Accepts any non-empty string up to 50 characters.
|
||||
// Use IsWellKnown() to check if it's a built-in type.
|
||||
func (d DependencyType) IsValid() bool {
|
||||
return len(d) > 0 && len(d) <= 50
|
||||
}
|
||||
|
||||
// IsWellKnown checks if the dependency type is a well-known constant.
|
||||
// Returns false for custom/user-defined types (which are still valid).
|
||||
func (d DependencyType) IsWellKnown() bool {
|
||||
switch d {
|
||||
case DepBlocks, DepRelated, DepParentChild, DepDiscoveredFrom,
|
||||
DepRepliesTo, DepRelatesTo, DepDuplicates, DepSupersedes:
|
||||
case DepBlocks, DepParentChild, DepRelated, DepDiscoveredFrom,
|
||||
DepRepliesTo, DepRelatesTo, DepDuplicates, DepSupersedes,
|
||||
DepAuthoredBy, DepAssignedTo, DepApprovedBy:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AffectsReadyWork returns true if this dependency type blocks work.
|
||||
// Only "blocks" and "parent-child" relationships affect the ready work calculation.
|
||||
func (d DependencyType) AffectsReadyWork() bool {
|
||||
return d == DepBlocks || d == DepParentChild
|
||||
}
|
||||
|
||||
// Label represents a tag on an issue
|
||||
type Label struct {
|
||||
IssueID string `json:"issue_id"`
|
||||
|
||||
@@ -415,6 +415,7 @@ func TestIssueTypeIsValid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDependencyTypeIsValid(t *testing.T) {
|
||||
// IsValid now accepts any non-empty string up to 50 chars (Decision 004)
|
||||
tests := []struct {
|
||||
depType DependencyType
|
||||
valid bool
|
||||
@@ -423,8 +424,17 @@ func TestDependencyTypeIsValid(t *testing.T) {
|
||||
{DepRelated, true},
|
||||
{DepParentChild, true},
|
||||
{DepDiscoveredFrom, true},
|
||||
{DependencyType("invalid"), false},
|
||||
{DependencyType(""), false},
|
||||
{DepRepliesTo, true},
|
||||
{DepRelatesTo, true},
|
||||
{DepDuplicates, true},
|
||||
{DepSupersedes, true},
|
||||
{DepAuthoredBy, true},
|
||||
{DepAssignedTo, true},
|
||||
{DepApprovedBy, true},
|
||||
{DependencyType("custom-type"), true}, // Custom types are now valid
|
||||
{DependencyType("any-string"), true}, // Any non-empty string is valid
|
||||
{DependencyType(""), false}, // Empty is still invalid
|
||||
{DependencyType("this-is-a-very-long-dependency-type-that-exceeds-fifty-characters"), false}, // Too long
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -436,6 +446,63 @@ func TestDependencyTypeIsValid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencyTypeIsWellKnown(t *testing.T) {
|
||||
tests := []struct {
|
||||
depType DependencyType
|
||||
wellKnown bool
|
||||
}{
|
||||
{DepBlocks, true},
|
||||
{DepRelated, true},
|
||||
{DepParentChild, true},
|
||||
{DepDiscoveredFrom, true},
|
||||
{DepRepliesTo, true},
|
||||
{DepRelatesTo, true},
|
||||
{DepDuplicates, true},
|
||||
{DepSupersedes, true},
|
||||
{DepAuthoredBy, true},
|
||||
{DepAssignedTo, true},
|
||||
{DepApprovedBy, true},
|
||||
{DependencyType("custom-type"), false},
|
||||
{DependencyType("unknown"), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.depType), func(t *testing.T) {
|
||||
if got := tt.depType.IsWellKnown(); got != tt.wellKnown {
|
||||
t.Errorf("DependencyType(%q).IsWellKnown() = %v, want %v", tt.depType, got, tt.wellKnown)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencyTypeAffectsReadyWork(t *testing.T) {
|
||||
tests := []struct {
|
||||
depType DependencyType
|
||||
affects bool
|
||||
}{
|
||||
{DepBlocks, true},
|
||||
{DepParentChild, true},
|
||||
{DepRelated, false},
|
||||
{DepDiscoveredFrom, false},
|
||||
{DepRepliesTo, false},
|
||||
{DepRelatesTo, false},
|
||||
{DepDuplicates, false},
|
||||
{DepSupersedes, false},
|
||||
{DepAuthoredBy, false},
|
||||
{DepAssignedTo, false},
|
||||
{DepApprovedBy, false},
|
||||
{DependencyType("custom-type"), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.depType), func(t *testing.T) {
|
||||
if got := tt.depType.AffectsReadyWork(); got != tt.affects {
|
||||
t.Errorf("DependencyType(%q).AffectsReadyWork() = %v, want %v", tt.depType, got, tt.affects)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueStructFields(t *testing.T) {
|
||||
// Test that all time fields work correctly
|
||||
now := time.Now()
|
||||
|
||||
Reference in New Issue
Block a user