hzl-core 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db/migrations/v2.d.ts +1 -1
- package/dist/db/migrations/v2.d.ts.map +1 -1
- package/dist/db/migrations/v2.js +6 -1
- package/dist/db/migrations/v2.js.map +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +2 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/projections/tasks-current.d.ts.map +1 -1
- package/dist/projections/tasks-current.js +10 -4
- package/dist/projections/tasks-current.js.map +1 -1
- package/dist/services/task-service.d.ts +43 -1
- package/dist/services/task-service.d.ts.map +1 -1
- package/dist/services/task-service.js +213 -1
- package/dist/services/task-service.js.map +1 -1
- package/dist/services/task-service.test.js +386 -1
- package/dist/services/task-service.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const MIGRATION_V2 = "\n--
|
|
1
|
+
export declare const MIGRATION_V2 = "\n-- Add terminal_at column to track when tasks enter terminal states (done, archived)\n-- Used for age-based pruning eligibility\nALTER TABLE tasks_current ADD COLUMN terminal_at TEXT;\n\n-- Create index for fast age-based queries during pruning\nCREATE INDEX IF NOT EXISTS idx_tasks_current_terminal_at ON tasks_current(terminal_at);\n";
|
|
2
2
|
//# sourceMappingURL=v2.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../../src/db/migrations/v2.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../../src/db/migrations/v2.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,sVAOxB,CAAC"}
|
package/dist/db/migrations/v2.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export const MIGRATION_V2 = `
|
|
2
|
-
--
|
|
2
|
+
-- Add terminal_at column to track when tasks enter terminal states (done, archived)
|
|
3
|
+
-- Used for age-based pruning eligibility
|
|
4
|
+
ALTER TABLE tasks_current ADD COLUMN terminal_at TEXT;
|
|
5
|
+
|
|
6
|
+
-- Create index for fast age-based queries during pruning
|
|
7
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_current_terminal_at ON tasks_current(terminal_at);
|
|
3
8
|
`;
|
|
4
9
|
//# sourceMappingURL=v2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2.js","sourceRoot":"","sources":["../../../src/db/migrations/v2.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG
|
|
1
|
+
{"version":3,"file":"v2.js","sourceRoot":"","sources":["../../../src/db/migrations/v2.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;CAO3B,CAAC"}
|
package/dist/db/schema.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const EVENTS_SCHEMA_V2 = "\n-- Append-only event store (source of truth)\nCREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n event_id TEXT NOT NULL UNIQUE,\n task_id TEXT NOT NULL,\n type TEXT NOT NULL,\n data TEXT NOT NULL CHECK (json_valid(data)),\n author TEXT,\n agent_id TEXT,\n session_id TEXT,\n correlation_id TEXT,\n causation_id TEXT,\n timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))\n);\n\n-- Append-only enforcement: prevent UPDATE on events\nCREATE TRIGGER IF NOT EXISTS events_no_update\nBEFORE UPDATE ON events\nBEGIN\n SELECT RAISE(ABORT, 'Events table is append-only: cannot UPDATE');\nEND;\n\n-- Append-only enforcement: prevent DELETE on events\nCREATE TRIGGER IF NOT EXISTS events_no_delete\nBEFORE DELETE ON events\nBEGIN\n SELECT RAISE(ABORT, 'Events table is append-only: cannot DELETE');\nEND;\n\n-- Global metadata (synced, immutable after creation)\nCREATE TABLE IF NOT EXISTS hzl_global_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Schema migrations tracking (append-only)\nCREATE TABLE IF NOT EXISTS schema_migrations (\n migration_id TEXT PRIMARY KEY,\n applied_at_ms INTEGER NOT NULL,\n checksum TEXT NOT NULL\n);\n\n-- Indexes for events\nCREATE INDEX IF NOT EXISTS idx_events_task_id ON events(task_id);\nCREATE INDEX IF NOT EXISTS idx_events_task_id_id ON events(task_id, id);\nCREATE INDEX IF NOT EXISTS idx_events_type ON events(type);\nCREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);\nCREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id);\n";
|
|
2
|
-
export declare const CACHE_SCHEMA_V1 = "\n-- Local metadata (per-device sync state)\nCREATE TABLE IF NOT EXISTS hzl_local_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Projection cursor (tracks last applied event)\nCREATE TABLE IF NOT EXISTS projection_cursor (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Track last applied event id per projection\nCREATE TABLE IF NOT EXISTS projection_state (\n name TEXT PRIMARY KEY,\n last_event_id INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL\n);\n\n-- Current-state projection for fast reads (rebuildable from events)\nCREATE TABLE IF NOT EXISTS tasks_current (\n task_id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n project TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('backlog','ready','in_progress','blocked','done','archived')),\n parent_id TEXT,\n description TEXT,\n links TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(links)),\n tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags)),\n priority INTEGER NOT NULL DEFAULT 0 CHECK (priority BETWEEN 0 AND 3),\n due_at TEXT,\n metadata TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(metadata)),\n claimed_at TEXT,\n assignee TEXT,\n progress INTEGER CHECK (progress >= 0 AND progress <= 100),\n lease_until TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\n-- Dependency edges for fast availability checks (rebuildable)\nCREATE TABLE IF NOT EXISTS task_dependencies (\n task_id TEXT NOT NULL,\n depends_on_id TEXT NOT NULL,\n PRIMARY KEY (task_id, depends_on_id)\n);\n\n-- Fast tag filtering (rebuildable)\nCREATE TABLE IF NOT EXISTS task_tags (\n task_id TEXT NOT NULL,\n tag TEXT NOT NULL,\n PRIMARY KEY (task_id, tag)\n);\n\n-- Fast access for steering comments (rebuildable)\nCREATE TABLE IF NOT EXISTS task_comments (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n author TEXT,\n agent_id TEXT,\n text TEXT NOT NULL,\n timestamp TEXT NOT NULL\n);\n\n-- Fast access for checkpoints (rebuildable)\nCREATE TABLE IF NOT EXISTS task_checkpoints (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(data)),\n timestamp TEXT NOT NULL\n);\n\n-- Full-text search over tasks (rebuildable)\nCREATE VIRTUAL TABLE IF NOT EXISTS task_search USING fts5(\n task_id UNINDEXED,\n title,\n description\n);\n\n-- Projects table (projection from events)\nCREATE TABLE IF NOT EXISTS projects (\n name TEXT PRIMARY KEY,\n description TEXT,\n is_protected INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_projects_protected ON projects(is_protected);\n\n-- Indexes for tasks_current\nCREATE INDEX IF NOT EXISTS idx_tasks_current_project_status ON tasks_current(project, status);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_status ON tasks_current(status);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_priority ON tasks_current(project, priority, created_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_claim_next ON tasks_current(project, status, priority DESC, created_at ASC, task_id ASC);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_stuck ON tasks_current(project, status, claimed_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_parent ON tasks_current(parent_id);\n\n-- Indexes for dependencies\nCREATE INDEX IF NOT EXISTS idx_deps_depends_on ON task_dependencies(depends_on_id);\n\n-- Indexes for tags/comments/checkpoints\nCREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag, task_id);\nCREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id, event_rowid);\nCREATE INDEX IF NOT EXISTS idx_task_checkpoints_task ON task_checkpoints(task_id, event_rowid);\n";
|
|
2
|
+
export declare const CACHE_SCHEMA_V1 = "\n-- Local metadata (per-device sync state)\nCREATE TABLE IF NOT EXISTS hzl_local_meta (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Projection cursor (tracks last applied event)\nCREATE TABLE IF NOT EXISTS projection_cursor (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n);\n\n-- Track last applied event id per projection\nCREATE TABLE IF NOT EXISTS projection_state (\n name TEXT PRIMARY KEY,\n last_event_id INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL\n);\n\n-- Current-state projection for fast reads (rebuildable from events)\nCREATE TABLE IF NOT EXISTS tasks_current (\n task_id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n project TEXT NOT NULL,\n status TEXT NOT NULL CHECK (status IN ('backlog','ready','in_progress','blocked','done','archived')),\n parent_id TEXT,\n description TEXT,\n links TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(links)),\n tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags)),\n priority INTEGER NOT NULL DEFAULT 0 CHECK (priority BETWEEN 0 AND 3),\n due_at TEXT,\n metadata TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(metadata)),\n claimed_at TEXT,\n assignee TEXT,\n progress INTEGER CHECK (progress >= 0 AND progress <= 100),\n lease_until TEXT,\n terminal_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\n-- Dependency edges for fast availability checks (rebuildable)\nCREATE TABLE IF NOT EXISTS task_dependencies (\n task_id TEXT NOT NULL,\n depends_on_id TEXT NOT NULL,\n PRIMARY KEY (task_id, depends_on_id)\n);\n\n-- Fast tag filtering (rebuildable)\nCREATE TABLE IF NOT EXISTS task_tags (\n task_id TEXT NOT NULL,\n tag TEXT NOT NULL,\n PRIMARY KEY (task_id, tag)\n);\n\n-- Fast access for steering comments (rebuildable)\nCREATE TABLE IF NOT EXISTS task_comments (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n author TEXT,\n agent_id TEXT,\n text TEXT NOT NULL,\n timestamp TEXT NOT NULL\n);\n\n-- Fast access for checkpoints (rebuildable)\nCREATE TABLE IF NOT EXISTS task_checkpoints (\n event_rowid INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(data)),\n timestamp TEXT NOT NULL\n);\n\n-- Full-text search over tasks (rebuildable)\nCREATE VIRTUAL TABLE IF NOT EXISTS task_search USING fts5(\n task_id UNINDEXED,\n title,\n description\n);\n\n-- Projects table (projection from events)\nCREATE TABLE IF NOT EXISTS projects (\n name TEXT PRIMARY KEY,\n description TEXT,\n is_protected INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n last_event_id INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_projects_protected ON projects(is_protected);\n\n-- Indexes for tasks_current\nCREATE INDEX IF NOT EXISTS idx_tasks_current_project_status ON tasks_current(project, status);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_status ON tasks_current(status);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_priority ON tasks_current(project, priority, created_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_claim_next ON tasks_current(project, status, priority DESC, created_at ASC, task_id ASC);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_stuck ON tasks_current(project, status, claimed_at);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_parent ON tasks_current(parent_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_current_terminal_at ON tasks_current(terminal_at);\n\n-- Indexes for dependencies\nCREATE INDEX IF NOT EXISTS idx_deps_depends_on ON task_dependencies(depends_on_id);\n\n-- Indexes for tags/comments/checkpoints\nCREATE INDEX IF NOT EXISTS idx_task_tags_tag ON task_tags(tag, task_id);\nCREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id, event_rowid);\nCREATE INDEX IF NOT EXISTS idx_task_checkpoints_task ON task_checkpoints(task_id, event_rowid);\n";
|
|
3
3
|
export declare const PRAGMAS = "\nPRAGMA journal_mode=WAL;\nPRAGMA synchronous=NORMAL;\nPRAGMA foreign_keys=ON;\nPRAGMA busy_timeout=5000;\n";
|
|
4
4
|
//# sourceMappingURL=schema.d.ts.map
|
package/dist/db/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,srDAiD5B,CAAC;AAGF,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,srDAiD5B,CAAC;AAGF,eAAO,MAAM,eAAe,qoIA8G3B,CAAC;AAEF,eAAO,MAAM,OAAO,iHAKnB,CAAC"}
|
package/dist/db/schema.js
CHANGED
|
@@ -87,6 +87,7 @@ CREATE TABLE IF NOT EXISTS tasks_current (
|
|
|
87
87
|
assignee TEXT,
|
|
88
88
|
progress INTEGER CHECK (progress >= 0 AND progress <= 100),
|
|
89
89
|
lease_until TEXT,
|
|
90
|
+
terminal_at TEXT,
|
|
90
91
|
created_at TEXT NOT NULL,
|
|
91
92
|
updated_at TEXT NOT NULL,
|
|
92
93
|
last_event_id INTEGER NOT NULL
|
|
@@ -150,6 +151,7 @@ CREATE INDEX IF NOT EXISTS idx_tasks_current_priority ON tasks_current(project,
|
|
|
150
151
|
CREATE INDEX IF NOT EXISTS idx_tasks_current_claim_next ON tasks_current(project, status, priority DESC, created_at ASC, task_id ASC);
|
|
151
152
|
CREATE INDEX IF NOT EXISTS idx_tasks_current_stuck ON tasks_current(project, status, claimed_at);
|
|
152
153
|
CREATE INDEX IF NOT EXISTS idx_tasks_current_parent ON tasks_current(parent_id);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_current_terminal_at ON tasks_current(terminal_at);
|
|
153
155
|
|
|
154
156
|
-- Indexes for dependencies
|
|
155
157
|
CREATE INDEX IF NOT EXISTS idx_deps_depends_on ON task_dependencies(depends_on_id);
|
package/dist/db/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiD/B,CAAC;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8G9B,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;CAKtB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export { SearchProjector } from './projections/search.js';
|
|
|
24
24
|
export { CommentsCheckpointsProjector } from './projections/comments-checkpoints.js';
|
|
25
25
|
export { ProjectsProjector } from './projections/projects.js';
|
|
26
26
|
export { rebuildAllProjections } from './projections/rebuild.js';
|
|
27
|
-
export { TaskService, TaskNotFoundError, TaskNotClaimableError, DependenciesNotDoneError, type CreateTaskInput, type EventContext, type Task, type ClaimTaskOptions, type ClaimNextOptions, type StealOptions, type StealResult, type StuckTask, type AvailableTask, type Comment, type Checkpoint, type TaskListItem, type TaskStats, } from './services/task-service.js';
|
|
27
|
+
export { TaskService, TaskNotFoundError, TaskNotClaimableError, DependenciesNotDoneError, CrossProjectDependencyError, type CreateTaskInput, type EventContext, type Task, type ClaimTaskOptions, type ClaimNextOptions, type StealOptions, type StealResult, type StuckTask, type AvailableTask, type Comment, type Checkpoint, type TaskListItem, type TaskStats, } from './services/task-service.js';
|
|
28
28
|
export { ProjectService, ProjectNotFoundError, ProtectedProjectError, ProjectHasTasksError, ProjectAlreadyExistsError, type Project, type CreateProjectOptions, } from './services/project-service.js';
|
|
29
29
|
export { ValidationService, type CycleNode, type MissingDep, type ValidationIssue, type ValidationResult, } from './services/validation-service.js';
|
|
30
30
|
export { SearchService, type SearchTaskResult, type SearchResult, type SearchOptions, } from './services/search-service.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ export { rebuildAllProjections } from './projections/rebuild.js';
|
|
|
34
34
|
// ============================================================================
|
|
35
35
|
// Services
|
|
36
36
|
// ============================================================================
|
|
37
|
-
export { TaskService, TaskNotFoundError, TaskNotClaimableError, DependenciesNotDoneError, } from './services/task-service.js';
|
|
37
|
+
export { TaskService, TaskNotFoundError, TaskNotClaimableError, DependenciesNotDoneError, CrossProjectDependencyError, } from './services/task-service.js';
|
|
38
38
|
export { ProjectService, ProjectNotFoundError, ProtectedProjectError, ProjectHasTasksError, ProjectAlreadyExistsError, } from './services/project-service.js';
|
|
39
39
|
export { ValidationService, } from './services/validation-service.js';
|
|
40
40
|
export { SearchService, } from './services/search-service.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EACL,YAAY,EAEb,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,yBAAyB,EACzB,cAAc,GAGf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,GAGhB,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EACL,gBAAgB,GAEjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,OAAO,EACL,UAAU,GAIX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,SAAS,EACT,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,GAMtB,MAAM,mBAAmB,CAAC;AAE3B,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAO3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,EACL,YAAY,EAEb,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,yBAAyB,EACzB,cAAc,GAGf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,GAGhB,MAAM,mBAAmB,CAAC;AAU3B,OAAO,EACL,gBAAgB,GAEjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,EACb,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,OAAO,EACL,UAAU,GAIX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,SAAS,EACT,UAAU,EACV,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,GAMtB,MAAM,mBAAmB,CAAC;AAE3B,+EAA+E;AAC/E,cAAc;AACd,+EAA+E;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAO3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,4BAA4B,EAAE,MAAM,uCAAuC,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,wBAAwB,EACxB,2BAA2B,GAc5B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,GAG1B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,iBAAiB,GAKlB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,aAAa,GAId,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,aAAa,GAEd,MAAM,8BAA8B,CAAC;AAEtC,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEtD,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E,OAAO,EACL,YAAY,EACZ,mBAAmB,GAEpB,MAAM,2BAA2B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks-current.d.ts","sourceRoot":"","sources":["../../src/projections/tasks-current.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAa5C,qBAAa,qBAAsB,YAAW,SAAS;IACrD,IAAI,SAAmB;IAEvB,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAuBjE,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAIlC,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,mBAAmB;
|
|
1
|
+
{"version":3,"file":"tasks-current.d.ts","sourceRoot":"","sources":["../../src/projections/tasks-current.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAa5C,qBAAa,qBAAsB,YAAW,SAAS;IACrD,IAAI,SAAmB;IAEvB,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAuBjE,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAIlC,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,mBAAmB;IAsG3B,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,wBAAwB;CAajC"}
|
|
@@ -41,6 +41,7 @@ export class TasksCurrentProjector {
|
|
|
41
41
|
handleStatusChanged(event, db) {
|
|
42
42
|
const data = event.data;
|
|
43
43
|
const toStatus = data.to;
|
|
44
|
+
const isTerminal = toStatus === TaskStatus.Done || toStatus === TaskStatus.Archived;
|
|
44
45
|
if (toStatus === TaskStatus.InProgress) {
|
|
45
46
|
const newAssignee = event.author || event.agent_id || null;
|
|
46
47
|
// Steal case: in_progress → in_progress - always overwrite assignee and claimed_at
|
|
@@ -94,25 +95,29 @@ export class TasksCurrentProjector {
|
|
|
94
95
|
}
|
|
95
96
|
else if (data.from === TaskStatus.InProgress || data.from === TaskStatus.Blocked) {
|
|
96
97
|
// When leaving in_progress/blocked: clear claimed_at and lease, but PRESERVE assignee
|
|
98
|
+
// If transitioning to terminal state, set terminal_at
|
|
97
99
|
db.prepare(`
|
|
98
100
|
UPDATE tasks_current SET
|
|
99
101
|
status = ?,
|
|
100
102
|
claimed_at = NULL,
|
|
101
103
|
lease_until = NULL,
|
|
104
|
+
terminal_at = CASE WHEN ? THEN ? ELSE terminal_at END,
|
|
102
105
|
updated_at = ?,
|
|
103
106
|
last_event_id = ?
|
|
104
107
|
WHERE task_id = ?
|
|
105
|
-
`).run(toStatus, event.timestamp, event.rowid, event.task_id);
|
|
108
|
+
`).run(toStatus, isTerminal ? 1 : 0, isTerminal ? event.timestamp : null, event.timestamp, event.rowid, event.task_id);
|
|
106
109
|
}
|
|
107
110
|
else {
|
|
108
|
-
// Generic status change
|
|
111
|
+
// Generic status change (including ready → done, backlog → archived, etc.)
|
|
112
|
+
// If transitioning to terminal state, set terminal_at
|
|
109
113
|
db.prepare(`
|
|
110
114
|
UPDATE tasks_current SET
|
|
111
115
|
status = ?,
|
|
116
|
+
terminal_at = CASE WHEN ? THEN ? ELSE terminal_at END,
|
|
112
117
|
updated_at = ?,
|
|
113
118
|
last_event_id = ?
|
|
114
119
|
WHERE task_id = ?
|
|
115
|
-
`).run(toStatus, event.timestamp, event.rowid, event.task_id);
|
|
120
|
+
`).run(toStatus, isTerminal ? 1 : 0, isTerminal ? event.timestamp : null, event.timestamp, event.rowid, event.task_id);
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
handleTaskMoved(event, db) {
|
|
@@ -143,10 +148,11 @@ export class TasksCurrentProjector {
|
|
|
143
148
|
db.prepare(`
|
|
144
149
|
UPDATE tasks_current SET
|
|
145
150
|
status = ?,
|
|
151
|
+
terminal_at = ?,
|
|
146
152
|
updated_at = ?,
|
|
147
153
|
last_event_id = ?
|
|
148
154
|
WHERE task_id = ?
|
|
149
|
-
`).run(TaskStatus.Archived, event.timestamp, event.rowid, event.task_id);
|
|
155
|
+
`).run(TaskStatus.Archived, event.timestamp, event.timestamp, event.rowid, event.task_id);
|
|
150
156
|
}
|
|
151
157
|
handleCheckpointRecorded(event, db) {
|
|
152
158
|
const data = event.data;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks-current.js","sourceRoot":"","sources":["../../src/projections/tasks-current.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,SAAS,EACT,UAAU,GAMX,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;AAE3D,MAAM,OAAO,qBAAqB;IAChC,IAAI,GAAG,eAAe,CAAC;IAEvB,KAAK,CAAC,KAA6B,EAAE,EAAqB;QACxD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,SAAS,CAAC,WAAW;gBACxB,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAClC,MAAM;YACR,KAAK,SAAS,CAAC,aAAa;gBAC1B,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBACpC,MAAM;YACR,KAAK,SAAS,CAAC,SAAS;gBACtB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAChC,MAAM;YACR,KAAK,SAAS,CAAC,WAAW;gBACxB,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAClC,MAAM;YACR,KAAK,SAAS,CAAC,YAAY;gBACzB,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBACnC,MAAM;YACR,KAAK,SAAS,CAAC,kBAAkB;gBAC/B,IAAI,CAAC,wBAAwB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBACzC,MAAM;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,EAAqB;QACzB,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IACvC,CAAC;IAEO,iBAAiB,CAAC,KAA6B,EAAE,EAAqB;QAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAuB,CAAC;QAC3C,EAAE,CAAC,OAAO,CAAC;;;;;;;KAOV,CAAC,CAAC,GAAG,CACJ,KAAK,CAAC,OAAO,EACb,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,OAAO,EACZ,UAAU,CAAC,OAAO,EAClB,IAAI,CAAC,SAAS,IAAI,IAAI,EACtB,IAAI,CAAC,WAAW,IAAI,IAAI,EACxB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAC/B,IAAI,CAAC,QAAQ,IAAI,CAAC,EAClB,IAAI,CAAC,MAAM,IAAI,IAAI,EACnB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EACnC,IAAI,CAAC,QAAQ,IAAI,IAAI,EACrB,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,KAAK,CACZ,CAAC;IACJ,CAAC;IAEO,mBAAmB,CAAC,KAA6B,EAAE,EAAqB;QAC9E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAyB,CAAC;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"tasks-current.js","sourceRoot":"","sources":["../../src/projections/tasks-current.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,6 +53,24 @@ export interface StuckTask {
|
|
|
53
53
|
assignee: string | null;
|
|
54
54
|
lease_until: string | null;
|
|
55
55
|
}
|
|
56
|
+
export interface PrunableTask {
|
|
57
|
+
task_id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
project: string;
|
|
60
|
+
status: 'done' | 'archived';
|
|
61
|
+
terminal_since: string;
|
|
62
|
+
parent_id: string | null;
|
|
63
|
+
}
|
|
64
|
+
export interface PruneOptions {
|
|
65
|
+
project?: string;
|
|
66
|
+
olderThanDays: number;
|
|
67
|
+
asOf?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface PruneResult {
|
|
70
|
+
pruned: PrunableTask[];
|
|
71
|
+
count: number;
|
|
72
|
+
eventsDeleted: number;
|
|
73
|
+
}
|
|
56
74
|
export interface AvailableTask {
|
|
57
75
|
task_id: string;
|
|
58
76
|
title: string;
|
|
@@ -121,14 +139,19 @@ export declare class TaskNotClaimableError extends Error {
|
|
|
121
139
|
export declare class DependenciesNotDoneError extends Error {
|
|
122
140
|
constructor(taskId: string, pendingDeps: string[]);
|
|
123
141
|
}
|
|
142
|
+
export declare class CrossProjectDependencyError extends Error {
|
|
143
|
+
constructor(taskProject: string, depTaskId: string, depProject: string);
|
|
144
|
+
}
|
|
124
145
|
export declare class TaskService {
|
|
125
146
|
private db;
|
|
126
147
|
private eventStore;
|
|
127
148
|
private projectionEngine;
|
|
128
149
|
private projectService?;
|
|
150
|
+
private eventsDb?;
|
|
129
151
|
private getIncompleteDepsStmt;
|
|
130
152
|
private getSubtasksStmt;
|
|
131
|
-
constructor(db: Database.Database,
|
|
153
|
+
constructor(db: Database.Database, // cache database
|
|
154
|
+
eventStore: EventStore, projectionEngine: ProjectionEngine, projectService?: ProjectService | undefined, eventsDb?: Database.Database | undefined);
|
|
132
155
|
createTask(input: CreateTaskInput, ctx?: EventContext): Task;
|
|
133
156
|
moveTask(taskId: string, toProject: string, ctx?: EventContext): Task;
|
|
134
157
|
/**
|
|
@@ -246,6 +269,25 @@ export declare class TaskService {
|
|
|
246
269
|
archivedSubtaskCount: number;
|
|
247
270
|
orphanedSubtaskCount: number;
|
|
248
271
|
};
|
|
272
|
+
/**
|
|
273
|
+
* Find tasks eligible for pruning (preview only, does not delete).
|
|
274
|
+
* A task is eligible if:
|
|
275
|
+
* 1. Status is 'done' or 'archived'
|
|
276
|
+
* 2. Has been in terminal state for >= olderThanDays
|
|
277
|
+
* 3. If parent: all children must also be eligible
|
|
278
|
+
* 4. If child: parent must also be eligible (atomic family)
|
|
279
|
+
* 5. If dependency target: all dependents must also be eligible
|
|
280
|
+
*/
|
|
281
|
+
previewPrunableTasks(opts: PruneOptions): PrunableTask[];
|
|
282
|
+
/**
|
|
283
|
+
* Permanently delete eligible tasks and their events.
|
|
284
|
+
* DANGEROUS: Breaks append-only event model.
|
|
285
|
+
* Recomputes eligibility inside the prune transaction to avoid TOCTOU.
|
|
286
|
+
*/
|
|
287
|
+
pruneEligible(opts: PruneOptions): PruneResult;
|
|
288
|
+
private deleteTasksFromProjections;
|
|
289
|
+
private deleteTasksFromEvents;
|
|
290
|
+
private recreateEventTriggers;
|
|
249
291
|
private rowToTask;
|
|
250
292
|
}
|
|
251
293
|
//# sourceMappingURL=task-service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-service.d.ts","sourceRoot":"","sources":["../../src/services/task-service.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"task-service.d.ts","sourceRoot":"","sources":["../../src/services/task-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,11 @@ export class DependenciesNotDoneError extends Error {
|
|
|
16
16
|
super(`Task ${taskId} has dependencies not done: ${pendingDeps.join(', ')}`);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
export class CrossProjectDependencyError extends Error {
|
|
20
|
+
constructor(taskProject, depTaskId, depProject) {
|
|
21
|
+
super(`Cross-project dependencies not supported: task in project '${taskProject}' cannot depend on task ${depTaskId} in project '${depProject}'`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
19
24
|
/**
|
|
20
25
|
* Validate progress value is an integer between 0 and 100.
|
|
21
26
|
* @throws Error if progress is invalid
|
|
@@ -30,13 +35,17 @@ export class TaskService {
|
|
|
30
35
|
eventStore;
|
|
31
36
|
projectionEngine;
|
|
32
37
|
projectService;
|
|
38
|
+
eventsDb;
|
|
33
39
|
getIncompleteDepsStmt;
|
|
34
40
|
getSubtasksStmt;
|
|
35
|
-
constructor(db,
|
|
41
|
+
constructor(db, // cache database
|
|
42
|
+
eventStore, projectionEngine, projectService, eventsDb // events database for pruning
|
|
43
|
+
) {
|
|
36
44
|
this.db = db;
|
|
37
45
|
this.eventStore = eventStore;
|
|
38
46
|
this.projectionEngine = projectionEngine;
|
|
39
47
|
this.projectService = projectService;
|
|
48
|
+
this.eventsDb = eventsDb;
|
|
40
49
|
this.getIncompleteDepsStmt = db.prepare(`
|
|
41
50
|
SELECT td.depends_on_id
|
|
42
51
|
FROM task_dependencies td
|
|
@@ -58,6 +67,15 @@ export class TaskService {
|
|
|
58
67
|
if (this.projectService) {
|
|
59
68
|
this.projectService.requireProject(input.project);
|
|
60
69
|
}
|
|
70
|
+
// Validate dependencies are in the same project (cross-project deps not supported)
|
|
71
|
+
if (input.depends_on && input.depends_on.length > 0) {
|
|
72
|
+
for (const depId of input.depends_on) {
|
|
73
|
+
const depTask = this.getTaskById(depId);
|
|
74
|
+
if (depTask && depTask.project !== input.project) {
|
|
75
|
+
throw new CrossProjectDependencyError(input.project, depId, depTask.project);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
61
79
|
const taskId = generateId();
|
|
62
80
|
const eventData = {
|
|
63
81
|
title: input.title,
|
|
@@ -933,6 +951,200 @@ export class TaskService {
|
|
|
933
951
|
};
|
|
934
952
|
});
|
|
935
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Find tasks eligible for pruning (preview only, does not delete).
|
|
956
|
+
* A task is eligible if:
|
|
957
|
+
* 1. Status is 'done' or 'archived'
|
|
958
|
+
* 2. Has been in terminal state for >= olderThanDays
|
|
959
|
+
* 3. If parent: all children must also be eligible
|
|
960
|
+
* 4. If child: parent must also be eligible (atomic family)
|
|
961
|
+
* 5. If dependency target: all dependents must also be eligible
|
|
962
|
+
*/
|
|
963
|
+
previewPrunableTasks(opts) {
|
|
964
|
+
const referenceTime = opts.asOf ? new Date(opts.asOf).getTime() : Date.now();
|
|
965
|
+
const thresholdTime = referenceTime - opts.olderThanDays * 24 * 60 * 60 * 1000;
|
|
966
|
+
const thresholdIso = new Date(thresholdTime).toISOString();
|
|
967
|
+
// Query to find eligible tasks using the complex family + dependency logic.
|
|
968
|
+
// NOTE: This query filters by project, which is safe because cross-project
|
|
969
|
+
// dependencies are explicitly prevented at task creation time (see createTask).
|
|
970
|
+
// All dependencies of a task are guaranteed to be in the same project.
|
|
971
|
+
const query = `
|
|
972
|
+
WITH family_status AS (
|
|
973
|
+
SELECT
|
|
974
|
+
t.task_id,
|
|
975
|
+
t.parent_id,
|
|
976
|
+
t.status,
|
|
977
|
+
t.terminal_at,
|
|
978
|
+
-- Check if task itself is terminal and old enough
|
|
979
|
+
CASE WHEN t.status IN ('done', 'archived')
|
|
980
|
+
AND t.terminal_at IS NOT NULL
|
|
981
|
+
AND t.terminal_at < ?
|
|
982
|
+
THEN 1 ELSE 0 END as self_eligible
|
|
983
|
+
FROM tasks_current t
|
|
984
|
+
WHERE (? IS NULL OR t.project = ?)
|
|
985
|
+
),
|
|
986
|
+
dep_blockers AS (
|
|
987
|
+
-- Tasks that are depended on by non-terminal tasks cannot be pruned.
|
|
988
|
+
-- Safe to use family_status (project-scoped) because cross-project
|
|
989
|
+
-- dependencies are not allowed.
|
|
990
|
+
SELECT DISTINCT d.depends_on_id AS task_id
|
|
991
|
+
FROM task_dependencies d
|
|
992
|
+
JOIN family_status t ON t.task_id = d.task_id
|
|
993
|
+
WHERE t.self_eligible = 0
|
|
994
|
+
),
|
|
995
|
+
family_eligible AS (
|
|
996
|
+
-- A task is family-eligible only if itself AND all family members are eligible
|
|
997
|
+
SELECT f.task_id, f.status, f.terminal_at
|
|
998
|
+
FROM family_status f
|
|
999
|
+
WHERE f.self_eligible = 1
|
|
1000
|
+
-- If has children, all must be eligible
|
|
1001
|
+
AND NOT EXISTS (
|
|
1002
|
+
SELECT 1 FROM family_status c
|
|
1003
|
+
WHERE c.parent_id = f.task_id AND c.self_eligible = 0
|
|
1004
|
+
)
|
|
1005
|
+
-- If has parent, parent must be eligible
|
|
1006
|
+
AND (f.parent_id IS NULL OR EXISTS (
|
|
1007
|
+
SELECT 1 FROM family_status p
|
|
1008
|
+
WHERE p.task_id = f.parent_id AND p.self_eligible = 1
|
|
1009
|
+
))
|
|
1010
|
+
-- Not depended on by any non-eligible task
|
|
1011
|
+
AND NOT EXISTS (SELECT 1 FROM dep_blockers b WHERE b.task_id = f.task_id)
|
|
1012
|
+
)
|
|
1013
|
+
SELECT
|
|
1014
|
+
task_id,
|
|
1015
|
+
title,
|
|
1016
|
+
project,
|
|
1017
|
+
status,
|
|
1018
|
+
terminal_at,
|
|
1019
|
+
parent_id
|
|
1020
|
+
FROM tasks_current
|
|
1021
|
+
WHERE task_id IN (SELECT task_id FROM family_eligible)
|
|
1022
|
+
ORDER BY terminal_at ASC
|
|
1023
|
+
`;
|
|
1024
|
+
const projectParam = opts.project ?? null;
|
|
1025
|
+
const result = this.db
|
|
1026
|
+
.prepare(query)
|
|
1027
|
+
.all(thresholdIso, projectParam, projectParam);
|
|
1028
|
+
return result.map(row => ({
|
|
1029
|
+
task_id: row.task_id,
|
|
1030
|
+
title: row.title,
|
|
1031
|
+
project: row.project,
|
|
1032
|
+
status: row.status,
|
|
1033
|
+
terminal_since: row.terminal_at,
|
|
1034
|
+
parent_id: row.parent_id,
|
|
1035
|
+
}));
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Permanently delete eligible tasks and their events.
|
|
1039
|
+
* DANGEROUS: Breaks append-only event model.
|
|
1040
|
+
* Recomputes eligibility inside the prune transaction to avoid TOCTOU.
|
|
1041
|
+
*/
|
|
1042
|
+
pruneEligible(opts) {
|
|
1043
|
+
if (!this.eventsDb) {
|
|
1044
|
+
throw new Error('TaskService: eventsDb not provided, cannot prune tasks');
|
|
1045
|
+
}
|
|
1046
|
+
return withWriteTransaction(this.eventsDb, () => {
|
|
1047
|
+
// First, get eligible tasks (recompute to avoid TOCTOU race)
|
|
1048
|
+
const eligibleTasks = this.previewPrunableTasks(opts);
|
|
1049
|
+
if (eligibleTasks.length === 0) {
|
|
1050
|
+
return {
|
|
1051
|
+
pruned: [],
|
|
1052
|
+
count: 0,
|
|
1053
|
+
eventsDeleted: 0,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const taskIds = eligibleTasks.map(t => t.task_id);
|
|
1057
|
+
// Delete events first (source of truth) - requires trigger bypass
|
|
1058
|
+
// This ordering is intentional: if projection deletion fails after events
|
|
1059
|
+
// are deleted, the projections can be rebuilt from remaining events.
|
|
1060
|
+
// The reverse (projections deleted, events remaining) would leave orphan
|
|
1061
|
+
// events that recreate projections on rebuild.
|
|
1062
|
+
const eventsDeleted = this.deleteTasksFromEvents(taskIds);
|
|
1063
|
+
// Delete from projections (cache.db) - derived state, recoverable
|
|
1064
|
+
this.deleteTasksFromProjections(taskIds);
|
|
1065
|
+
return {
|
|
1066
|
+
pruned: eligibleTasks,
|
|
1067
|
+
count: eligibleTasks.length,
|
|
1068
|
+
eventsDeleted,
|
|
1069
|
+
};
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
deleteTasksFromProjections(taskIds) {
|
|
1073
|
+
// Delete from all projection tables in order
|
|
1074
|
+
const tables = [
|
|
1075
|
+
'task_comments',
|
|
1076
|
+
'task_checkpoints',
|
|
1077
|
+
'task_tags',
|
|
1078
|
+
'task_dependencies', // both directions
|
|
1079
|
+
'task_search',
|
|
1080
|
+
'tasks_current',
|
|
1081
|
+
];
|
|
1082
|
+
for (const table of tables) {
|
|
1083
|
+
// Create temp table to avoid SQLite parameter limits
|
|
1084
|
+
this.db.exec('CREATE TEMP TABLE prune_targets (task_id TEXT PRIMARY KEY)');
|
|
1085
|
+
const insert = this.db.prepare('INSERT INTO prune_targets (task_id) VALUES (?)');
|
|
1086
|
+
for (const id of taskIds) {
|
|
1087
|
+
insert.run(id);
|
|
1088
|
+
}
|
|
1089
|
+
if (table === 'task_dependencies') {
|
|
1090
|
+
// Delete both directions
|
|
1091
|
+
this.db.exec('DELETE FROM task_dependencies WHERE task_id IN (SELECT task_id FROM prune_targets) OR depends_on_id IN (SELECT task_id FROM prune_targets)');
|
|
1092
|
+
}
|
|
1093
|
+
else if (table === 'task_search') {
|
|
1094
|
+
// FTS table uses different syntax
|
|
1095
|
+
this.db.exec('DELETE FROM task_search WHERE task_id IN (SELECT task_id FROM prune_targets)');
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
this.db.exec(`DELETE FROM ${table} WHERE task_id IN (SELECT task_id FROM prune_targets)`);
|
|
1099
|
+
}
|
|
1100
|
+
this.db.exec('DROP TABLE prune_targets');
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
deleteTasksFromEvents(taskIds) {
|
|
1104
|
+
const eventsDb = this.eventsDb; // Non-null assertion safe: checked in pruneEligible
|
|
1105
|
+
// Disable triggers
|
|
1106
|
+
eventsDb.exec('DROP TRIGGER IF EXISTS events_no_delete');
|
|
1107
|
+
eventsDb.exec('DROP TRIGGER IF EXISTS events_no_update');
|
|
1108
|
+
try {
|
|
1109
|
+
// Create temp table to avoid SQLite parameter limits on large prune sets
|
|
1110
|
+
eventsDb.exec('CREATE TEMP TABLE prune_targets (task_id TEXT PRIMARY KEY)');
|
|
1111
|
+
const insert = eventsDb.prepare('INSERT INTO prune_targets (task_id) VALUES (?)');
|
|
1112
|
+
for (const id of taskIds) {
|
|
1113
|
+
insert.run(id);
|
|
1114
|
+
}
|
|
1115
|
+
const result = eventsDb
|
|
1116
|
+
.prepare('DELETE FROM events WHERE task_id IN (SELECT task_id FROM prune_targets)')
|
|
1117
|
+
.run();
|
|
1118
|
+
eventsDb.exec('DROP TABLE prune_targets');
|
|
1119
|
+
// Re-enable triggers
|
|
1120
|
+
this.recreateEventTriggers();
|
|
1121
|
+
return result.changes;
|
|
1122
|
+
}
|
|
1123
|
+
catch (err) {
|
|
1124
|
+
// Re-enable triggers even on error
|
|
1125
|
+
this.recreateEventTriggers();
|
|
1126
|
+
throw err;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
recreateEventTriggers() {
|
|
1130
|
+
const eventsDb = this.eventsDb; // Non-null assertion safe: checked in pruneEligible
|
|
1131
|
+
const EVENTS_TRIGGERS_SQL = `
|
|
1132
|
+
-- Append-only enforcement: prevent UPDATE on events
|
|
1133
|
+
CREATE TRIGGER IF NOT EXISTS events_no_update
|
|
1134
|
+
BEFORE UPDATE ON events
|
|
1135
|
+
BEGIN
|
|
1136
|
+
SELECT RAISE(ABORT, 'Events table is append-only: cannot UPDATE');
|
|
1137
|
+
END;
|
|
1138
|
+
|
|
1139
|
+
-- Append-only enforcement: prevent DELETE on events
|
|
1140
|
+
CREATE TRIGGER IF NOT EXISTS events_no_delete
|
|
1141
|
+
BEFORE DELETE ON events
|
|
1142
|
+
BEGIN
|
|
1143
|
+
SELECT RAISE(ABORT, 'Events table is append-only: cannot DELETE');
|
|
1144
|
+
END;
|
|
1145
|
+
`;
|
|
1146
|
+
eventsDb.exec(EVENTS_TRIGGERS_SQL);
|
|
1147
|
+
}
|
|
936
1148
|
rowToTask(row) {
|
|
937
1149
|
return {
|
|
938
1150
|
task_id: row.task_id,
|