feat: add crystallizes column to sqlite storage
Adds crystallizes column for work economics (compounds vs evaporates) per Decision 006. Includes migration 036 and updates to all INSERT/SELECT queries in issues.go, queries.go, dependencies.go, and transaction.go. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
0ed349b3ed
commit
f5cd36752d
@@ -249,7 +249,7 @@ func (s *SQLiteStorage) GetDependenciesWithMetadata(ctx context.Context, issueID
|
||||
i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes,
|
||||
i.created_at, i.created_by, i.owner, i.updated_at, i.closed_at, i.external_ref, i.source_repo,
|
||||
i.deleted_at, i.deleted_by, i.delete_reason, i.original_type,
|
||||
i.sender, i.ephemeral, i.pinned, i.is_template,
|
||||
i.sender, i.ephemeral, i.pinned, i.is_template, i.crystallizes,
|
||||
i.await_type, i.await_id, i.timeout_ns, i.waiters,
|
||||
d.type
|
||||
FROM issues i
|
||||
@@ -272,7 +272,7 @@ func (s *SQLiteStorage) GetDependentsWithMetadata(ctx context.Context, issueID s
|
||||
i.status, i.priority, i.issue_type, i.assignee, i.estimated_minutes,
|
||||
i.created_at, i.created_by, i.owner, i.updated_at, i.closed_at, i.external_ref, i.source_repo,
|
||||
i.deleted_at, i.deleted_by, i.delete_reason, i.original_type,
|
||||
i.sender, i.ephemeral, i.pinned, i.is_template,
|
||||
i.sender, i.ephemeral, i.pinned, i.is_template, i.crystallizes,
|
||||
i.await_type, i.await_id, i.timeout_ns, i.waiters,
|
||||
d.type
|
||||
FROM issues i
|
||||
@@ -879,6 +879,8 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
var pinned sql.NullInt64
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Crystallizes field (work economics)
|
||||
var crystallizes sql.NullInt64
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
@@ -891,7 +893,7 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
|
||||
&issue.CreatedAt, &issue.CreatedBy, &owner, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo, &closeReason,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
&sender, &wisp, &pinned, &isTemplate,
|
||||
&sender, &wisp, &pinned, &isTemplate, &crystallizes,
|
||||
&awaitType, &awaitID, &timeoutNs, &waiters,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -948,6 +950,10 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Crystallizes field (work economics)
|
||||
if crystallizes.Valid && crystallizes.Int64 != 0 {
|
||||
issue.Crystallizes = true
|
||||
}
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
@@ -1005,6 +1011,8 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
var pinned sql.NullInt64
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Crystallizes field (work economics)
|
||||
var crystallizes sql.NullInt64
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
@@ -1018,7 +1026,7 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
&issue.Priority, &issue.IssueType, &assignee, &estimatedMinutes,
|
||||
&issue.CreatedAt, &issue.CreatedBy, &owner, &issue.UpdatedAt, &closedAt, &externalRef, &sourceRepo,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
&sender, &wisp, &pinned, &isTemplate,
|
||||
&sender, &wisp, &pinned, &isTemplate, &crystallizes,
|
||||
&awaitType, &awaitID, &timeoutNs, &waiters,
|
||||
&depType,
|
||||
)
|
||||
@@ -1073,6 +1081,10 @@ func (s *SQLiteStorage) scanIssuesWithDependencyType(ctx context.Context, rows *
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Crystallizes field (work economics)
|
||||
if crystallizes.Valid && crystallizes.Int64 != 0 {
|
||||
issue.Crystallizes = true
|
||||
}
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
|
||||
@@ -41,6 +41,10 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
|
||||
if issue.IsTemplate {
|
||||
isTemplate = 1
|
||||
}
|
||||
crystallizes := 0
|
||||
if issue.Crystallizes {
|
||||
crystallizes = 1
|
||||
}
|
||||
|
||||
_, err := conn.ExecContext(ctx, `
|
||||
INSERT OR IGNORE INTO issues (
|
||||
@@ -48,7 +52,7 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
|
||||
status, priority, issue_type, assignee, estimated_minutes,
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters, mol_type,
|
||||
event_kind, actor, target, payload,
|
||||
due_at, defer_until
|
||||
@@ -60,7 +64,7 @@ func insertIssue(ctx context.Context, conn *sql.Conn, issue *types.Issue) error
|
||||
issue.EstimatedMinutes, issue.CreatedAt, issue.CreatedBy, issue.Owner, issue.UpdatedAt,
|
||||
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
|
||||
issue.DeletedAt, issue.DeletedBy, issue.DeleteReason, issue.OriginalType,
|
||||
issue.Sender, wisp, pinned, isTemplate,
|
||||
issue.Sender, wisp, pinned, isTemplate, crystallizes,
|
||||
issue.AwaitType, issue.AwaitID, int64(issue.Timeout), formatJSONStringArray(issue.Waiters),
|
||||
string(issue.MolType),
|
||||
issue.EventKind, issue.Actor, issue.Target, issue.Payload,
|
||||
@@ -99,6 +103,10 @@ func insertIssueStrict(ctx context.Context, conn *sql.Conn, issue *types.Issue)
|
||||
if issue.IsTemplate {
|
||||
isTemplate = 1
|
||||
}
|
||||
crystallizes := 0
|
||||
if issue.Crystallizes {
|
||||
crystallizes = 1
|
||||
}
|
||||
|
||||
_, err := conn.ExecContext(ctx, `
|
||||
INSERT INTO issues (
|
||||
@@ -106,7 +114,7 @@ func insertIssueStrict(ctx context.Context, conn *sql.Conn, issue *types.Issue)
|
||||
status, priority, issue_type, assignee, estimated_minutes,
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters, mol_type,
|
||||
event_kind, actor, target, payload,
|
||||
due_at, defer_until
|
||||
@@ -118,7 +126,7 @@ func insertIssueStrict(ctx context.Context, conn *sql.Conn, issue *types.Issue)
|
||||
issue.EstimatedMinutes, issue.CreatedAt, issue.CreatedBy, issue.Owner, issue.UpdatedAt,
|
||||
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
|
||||
issue.DeletedAt, issue.DeletedBy, issue.DeleteReason, issue.OriginalType,
|
||||
issue.Sender, wisp, pinned, isTemplate,
|
||||
issue.Sender, wisp, pinned, isTemplate, crystallizes,
|
||||
issue.AwaitType, issue.AwaitID, int64(issue.Timeout), formatJSONStringArray(issue.Waiters),
|
||||
string(issue.MolType),
|
||||
issue.EventKind, issue.Actor, issue.Target, issue.Payload,
|
||||
@@ -138,7 +146,7 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
|
||||
status, priority, issue_type, assignee, estimated_minutes,
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters, mol_type,
|
||||
event_kind, actor, target, payload,
|
||||
due_at, defer_until
|
||||
@@ -167,6 +175,10 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
|
||||
if issue.IsTemplate {
|
||||
isTemplate = 1
|
||||
}
|
||||
crystallizes := 0
|
||||
if issue.Crystallizes {
|
||||
crystallizes = 1
|
||||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx,
|
||||
issue.ID, issue.ContentHash, issue.Title, issue.Description, issue.Design,
|
||||
@@ -175,7 +187,7 @@ func insertIssues(ctx context.Context, conn *sql.Conn, issues []*types.Issue) er
|
||||
issue.EstimatedMinutes, issue.CreatedAt, issue.CreatedBy, issue.Owner, issue.UpdatedAt,
|
||||
issue.ClosedAt, issue.ExternalRef, sourceRepo, issue.CloseReason,
|
||||
issue.DeletedAt, issue.DeletedBy, issue.DeleteReason, issue.OriginalType,
|
||||
issue.Sender, wisp, pinned, isTemplate,
|
||||
issue.Sender, wisp, pinned, isTemplate, crystallizes,
|
||||
issue.AwaitType, issue.AwaitID, int64(issue.Timeout), formatJSONStringArray(issue.Waiters),
|
||||
string(issue.MolType),
|
||||
issue.EventKind, issue.Actor, issue.Target, issue.Payload,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// MigrateCrystallizesColumn adds the crystallizes column to the issues table.
|
||||
// Crystallizes tracks whether work compounds over time (true: code, features)
|
||||
// or evaporates (false: ops, support). Per Decision 006, this affects CV weighting.
|
||||
// Default is false (conservative - work evaporates unless explicitly marked).
|
||||
func MigrateCrystallizesColumn(db *sql.DB) error {
|
||||
// Check if column already exists
|
||||
var columnExists bool
|
||||
err := db.QueryRow(`
|
||||
SELECT COUNT(*) > 0
|
||||
FROM pragma_table_info('issues')
|
||||
WHERE name = 'crystallizes'
|
||||
`).Scan(&columnExists)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check crystallizes column: %w", err)
|
||||
}
|
||||
|
||||
if columnExists {
|
||||
// Column already exists (e.g. created by new schema)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add the crystallizes column
|
||||
_, err = db.Exec(`ALTER TABLE issues ADD COLUMN crystallizes INTEGER DEFAULT 0`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add crystallizes column: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -273,6 +273,8 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
var pinned sql.NullInt64
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Crystallizes field (work economics)
|
||||
var crystallizes sql.NullInt64
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
@@ -305,7 +307,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref,
|
||||
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters,
|
||||
hook_bead, role_bead, agent_state, last_activity, role_type, rig, mol_type,
|
||||
event_kind, actor, target, payload,
|
||||
@@ -319,7 +321,7 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
&issue.CreatedAt, &issue.CreatedBy, &owner, &issue.UpdatedAt, &closedAt, &externalRef,
|
||||
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
&sender, &wisp, &pinned, &isTemplate,
|
||||
&sender, &wisp, &pinned, &isTemplate, &crystallizes,
|
||||
&awaitType, &awaitID, &timeoutNs, &waiters,
|
||||
&hookBead, &roleBead, &agentState, &lastActivity, &roleType, &rig, &molType,
|
||||
&eventKind, &actor, &target, &payload,
|
||||
@@ -392,6 +394,10 @@ func (s *SQLiteStorage) GetIssue(ctx context.Context, id string) (*types.Issue,
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Crystallizes field (work economics)
|
||||
if crystallizes.Valid && crystallizes.Int64 != 0 {
|
||||
issue.Crystallizes = true
|
||||
}
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
@@ -558,6 +564,8 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
||||
var pinned sql.NullInt64
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Crystallizes field (work economics)
|
||||
var crystallizes sql.NullInt64
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
@@ -571,7 +579,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref,
|
||||
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters
|
||||
FROM issues
|
||||
WHERE external_ref = ?
|
||||
@@ -582,7 +590,7 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
||||
&issue.CreatedAt, &issue.CreatedBy, &owner, &issue.UpdatedAt, &closedAt, &externalRefCol,
|
||||
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
&sender, &wisp, &pinned, &isTemplate,
|
||||
&sender, &wisp, &pinned, &isTemplate, &crystallizes,
|
||||
&awaitType, &awaitID, &timeoutNs, &waiters,
|
||||
)
|
||||
|
||||
@@ -652,6 +660,10 @@ func (s *SQLiteStorage) GetIssueByExternalRef(ctx context.Context, externalRef s
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Crystallizes field (work economics)
|
||||
if crystallizes.Valid && crystallizes.Int64 != 0 {
|
||||
issue.Crystallizes = true
|
||||
}
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
@@ -1938,7 +1950,7 @@ func (s *SQLiteStorage) SearchIssues(ctx context.Context, query string, filter t
|
||||
status, priority, issue_type, assignee, estimated_minutes,
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters
|
||||
FROM issues
|
||||
%s
|
||||
|
||||
@@ -37,6 +37,8 @@ CREATE TABLE IF NOT EXISTS issues (
|
||||
pinned INTEGER DEFAULT 0,
|
||||
-- Template field (beads-1ra)
|
||||
is_template INTEGER DEFAULT 0,
|
||||
-- Work economics field (bd-fqze8) - HOP Decision 006
|
||||
crystallizes INTEGER DEFAULT 0,
|
||||
-- Molecule type field (bd-oxgi)
|
||||
mol_type TEXT DEFAULT '',
|
||||
-- Work type field (Decision 006: mutex vs open_competition)
|
||||
|
||||
@@ -330,7 +330,7 @@ func (t *sqliteTxStorage) GetIssue(ctx context.Context, id string) (*types.Issue
|
||||
created_at, created_by, owner, updated_at, closed_at, external_ref,
|
||||
compaction_level, compacted_at, compacted_at_commit, original_size, source_repo, close_reason,
|
||||
deleted_at, deleted_by, delete_reason, original_type,
|
||||
sender, ephemeral, pinned, is_template,
|
||||
sender, ephemeral, pinned, is_template, crystallizes,
|
||||
await_type, await_id, timeout_ns, waiters
|
||||
FROM issues
|
||||
WHERE id = ?
|
||||
@@ -1308,6 +1308,8 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
|
||||
var pinned sql.NullInt64
|
||||
// Template field
|
||||
var isTemplate sql.NullInt64
|
||||
// Crystallizes field (work economics)
|
||||
var crystallizes sql.NullInt64
|
||||
// Gate fields
|
||||
var awaitType sql.NullString
|
||||
var awaitID sql.NullString
|
||||
@@ -1321,7 +1323,7 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
|
||||
&issue.CreatedAt, &issue.CreatedBy, &owner, &issue.UpdatedAt, &closedAt, &externalRef,
|
||||
&issue.CompactionLevel, &compactedAt, &compactedAtCommit, &originalSize, &sourceRepo, &closeReason,
|
||||
&deletedAt, &deletedBy, &deleteReason, &originalType,
|
||||
&sender, &wisp, &pinned, &isTemplate,
|
||||
&sender, &wisp, &pinned, &isTemplate, &crystallizes,
|
||||
&awaitType, &awaitID, &timeoutNs, &waiters,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1387,6 +1389,10 @@ func scanIssueRow(row scanner) (*types.Issue, error) {
|
||||
if isTemplate.Valid && isTemplate.Int64 != 0 {
|
||||
issue.IsTemplate = true
|
||||
}
|
||||
// Crystallizes field (work economics)
|
||||
if crystallizes.Valid && crystallizes.Int64 != 0 {
|
||||
issue.Crystallizes = true
|
||||
}
|
||||
// Gate fields
|
||||
if awaitType.Valid {
|
||||
issue.AwaitType = awaitType.String
|
||||
|
||||
Reference in New Issue
Block a user