@devflow-tools/database 0.17.1 → 0.17.3
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/CHANGELOG.md +35 -0
- package/__tests__/database.retrieval-ledger.test.ts +15 -0
- package/__tests__/database.task-runtime.test.ts +73 -0
- package/__tests__/database.work-queue.test.ts +34 -0
- package/dist/database.d.ts +46 -0
- package/dist/database.js +374 -41
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -1
- package/dist/node-sqlite.d.ts +1 -1
- package/dist/node-sqlite.js +8 -4
- package/dist/retrieval-ledger.d.ts +8 -1
- package/dist/task-runtime.d.ts +64 -0
- package/dist/task-runtime.js +106 -0
- package/dist/work-queue.d.ts +19 -2
- package/package.json +1 -1
- package/src/database.ts +413 -44
- package/src/index.ts +18 -0
- package/src/node-sqlite.ts +8 -4
- package/src/retrieval-ledger.ts +9 -2
- package/src/task-runtime.ts +169 -0
- package/src/work-queue.ts +21 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,41 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.17.3](https://github.com/shilongfeicool/dev-flow/compare/v0.17.2...v0.17.3) (2026-08-03)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **runtime:** enforce evidence-driven task truth ([6a8f84a](https://github.com/shilongfeicool/dev-flow/commit/6a8f84abf62666dfb7b8643fa88a5e9489940fce))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **evidence:** attribute consumed and verified retrieval ([65e5828](https://github.com/shilongfeicool/dev-flow/commit/65e5828de6d8455a2712ff78ea411aa2bf8be829))
|
|
17
|
+
* **runtime:** persist and reduce task events ([f1966c5](https://github.com/shilongfeicool/dev-flow/commit/f1966c5eaa6c023b88c4c52b3109657b7ad8cca1))
|
|
18
|
+
* **runtime:** shadow online task reduction ([c15088c](https://github.com/shilongfeicool/dev-flow/commit/c15088c9fc9434bba86bcbceeb107da36f2d9606))
|
|
19
|
+
* **semantic:** harden durable memory and knowledge work ([69eab16](https://github.com/shilongfeicool/dev-flow/commit/69eab16012ad7b3cb6a7a38ddcb1c7c1fc30054a))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## [0.17.2](https://github.com/shilongfeicool/dev-flow/compare/v0.17.1...v0.17.2) (2026-08-01)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* **reliability:** close semantic lifecycle gaps ([08bae87](https://github.com/shilongfeicool/dev-flow/commit/08bae8742c5907dffdd476679e70c86cc373a7e8))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
* **reliability:** strengthen semantic retrieval closure ([b979ab3](https://github.com/shilongfeicool/dev-flow/commit/b979ab3ec790e3d7119db512902db90a9567667d))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
6
41
|
## [0.17.1](https://github.com/shilongfeicool/dev-flow/compare/v0.16.28...v0.17.1) (2026-07-31)
|
|
7
42
|
|
|
8
43
|
|
|
@@ -35,6 +35,21 @@ describe('retrieval ledger', () => {
|
|
|
35
35
|
database.close();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
it('persists v1 provenance and reads legacy adopted rows as consumed', () => {
|
|
39
|
+
const database = createDatabase();
|
|
40
|
+
database.appendRetrievalLedgerEvent(event({
|
|
41
|
+
id: 'legacy-adopted', stage: 'adopted', taskSpecHash: 'spec:1', actor: 'tool:read',
|
|
42
|
+
sourceVersion: 'v1', sourceContentHash: 'sha256:1', evidenceIds: ['memory-a'],
|
|
43
|
+
reasonCode: 'structural_evidence_ref',
|
|
44
|
+
}));
|
|
45
|
+
expect(database.listRetrievalLedgerEvents({ projectRoot: '/project' })[0]).toMatchObject({
|
|
46
|
+
schemaVersion: 'retrieval-ledger-event.v1', stage: 'consumed', taskSpecHash: 'spec:1',
|
|
47
|
+
actor: 'tool:read', sourceVersion: 'v1', sourceContentHash: 'sha256:1',
|
|
48
|
+
evidenceIds: ['memory-a'], reasonCode: 'structural_evidence_ref',
|
|
49
|
+
});
|
|
50
|
+
database.close();
|
|
51
|
+
});
|
|
52
|
+
|
|
38
53
|
function createDatabase(): DevFlowDatabase {
|
|
39
54
|
const directory = mkdtempSync(join(tmpdir(), 'devflow-retrieval-ledger-'));
|
|
40
55
|
directories.push(directory);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { DevFlowDatabase } from '../src/database';
|
|
6
|
+
import { stableTaskRuntimeHash, type TaskRuntimeEventRecord, type TaskRuntimeSnapshotRecord } from '../src/task-runtime';
|
|
7
|
+
|
|
8
|
+
const identity = {
|
|
9
|
+
projectRoot: '/project', projectId: 'project:1', hostId: 'claude-code',
|
|
10
|
+
sessionId: 'session:1', turnId: 'turn:1', requestId: 'request:1',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function event(overrides: Partial<TaskRuntimeEventRecord> = {}): TaskRuntimeEventRecord {
|
|
14
|
+
return {
|
|
15
|
+
schemaVersion: 'task-runtime-event.v1', producer: 'test', producerVersion: '1.0.0',
|
|
16
|
+
eventId: 'event:1', sequence: 1, identity, taskSpecHash: 'spec:1',
|
|
17
|
+
kind: 'task.spec.bound', payload: {}, createdAt: 1, ...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function snapshot(lastEventSequence = 1): TaskRuntimeSnapshotRecord {
|
|
22
|
+
const body = {
|
|
23
|
+
schemaVersion: 'task-runtime-snapshot.v1' as const,
|
|
24
|
+
producer: 'test', producerVersion: '1.0.0', identity, taskSpecHash: 'spec:1',
|
|
25
|
+
lastEventSequence,
|
|
26
|
+
artifacts: { requiredIdentityIds: [], resolvedIdentityIds: [], ambiguousIdentityIds: [] },
|
|
27
|
+
channels: {},
|
|
28
|
+
action: { requirement: 'not_required' as const, state: 'satisfied' as const },
|
|
29
|
+
verification: { requirement: 'not_required' as const, state: 'satisfied' as const },
|
|
30
|
+
memory: { requirement: 'required' as const, state: 'satisfied' as const },
|
|
31
|
+
durableWorkIds: [], terminal: 'completed' as const, runtimeHealth: 'healthy' as const,
|
|
32
|
+
updatedAt: lastEventSequence,
|
|
33
|
+
};
|
|
34
|
+
return { ...body, snapshotHash: stableTaskRuntimeHash(body) };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('DevFlowDatabase task runtime persistence', () => {
|
|
38
|
+
let directory: string;
|
|
39
|
+
let database: DevFlowDatabase;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
directory = mkdtempSync(join(tmpdir(), 'devflow-task-runtime-'));
|
|
43
|
+
database = new DevFlowDatabase(directory);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
database.close();
|
|
48
|
+
rmSync(directory, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('makes identical event replay idempotent and rejects conflicting IDs or sequences', () => {
|
|
52
|
+
const first = database.appendTaskRuntimeEvent(event());
|
|
53
|
+
expect(database.appendTaskRuntimeEvent(event())).toEqual(first);
|
|
54
|
+
expect(database.listTaskRuntimeEvents('/project', 'session:1', 'turn:1')).toEqual([first]);
|
|
55
|
+
|
|
56
|
+
expect(() => database.appendTaskRuntimeEvent(event({ payload: { changed: true } })))
|
|
57
|
+
.toThrow('TASK_RUNTIME_EVENT_CONFLICT:event:1');
|
|
58
|
+
expect(() => database.appendTaskRuntimeEvent(event({ eventId: 'event:2' })))
|
|
59
|
+
.toThrow('TASK_RUNTIME_SEQUENCE_CONFLICT:1');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('round-trips snapshots, rejects regression, and supports deterministic rebuild deletion', () => {
|
|
63
|
+
database.appendTaskRuntimeEvent(event());
|
|
64
|
+
const current = database.putTaskRuntimeSnapshot(snapshot(1));
|
|
65
|
+
expect(database.getTaskRuntimeSnapshot('/project', 'session:1', 'turn:1')).toEqual(current);
|
|
66
|
+
expect(() => database.putTaskRuntimeSnapshot(snapshot(0))).toThrow('TASK_RUNTIME_SNAPSHOT_REGRESSION');
|
|
67
|
+
|
|
68
|
+
expect(database.deleteTaskRuntimeSnapshot('/project', 'session:1', 'turn:1')).toBe(true);
|
|
69
|
+
expect(database.getTaskRuntimeSnapshot('/project', 'session:1', 'turn:1')).toBeNull();
|
|
70
|
+
expect(database.listTaskRuntimeEvents('/project', 'session:1', 'turn:1')).toEqual([event()]);
|
|
71
|
+
expect(database.putTaskRuntimeSnapshot(snapshot(1))).toEqual(current);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -166,6 +166,40 @@ describe('DevFlowDatabase durable work queue', () => {
|
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
+
it('serializes project mutations, cancels stale work, and preserves dead-letter replay lineage', () => {
|
|
170
|
+
expect(database.acquireProjectMutationLease({
|
|
171
|
+
projectRoot, mutationKind: 'knowledge_publish', owner: 'worker-a', leaseMs: 100, now: 10,
|
|
172
|
+
})).toBe(true);
|
|
173
|
+
expect(database.acquireProjectMutationLease({
|
|
174
|
+
projectRoot, mutationKind: 'knowledge_publish', owner: 'worker-b', leaseMs: 100, now: 50,
|
|
175
|
+
})).toBe(false);
|
|
176
|
+
expect(database.acquireProjectMutationLease({
|
|
177
|
+
projectRoot, mutationKind: 'knowledge_publish', owner: 'worker-b', leaseMs: 100, now: 111,
|
|
178
|
+
})).toBe(true);
|
|
179
|
+
|
|
180
|
+
const stale = database.enqueueWork({
|
|
181
|
+
idempotencyKey: 'knowledge:stale', kind: 'knowledge.ingest', projectRoot,
|
|
182
|
+
sourceHash: 'source:v1', taskSpecHash: 'spec:v1', payload: {}, nextAttemptAt: 200,
|
|
183
|
+
});
|
|
184
|
+
expect(stale.maxAttempts).toBe(3);
|
|
185
|
+
database.leaseWork({ projectRoot, owner: 'worker-b', workItemIds: [stale.id], limit: 1, leaseMs: 100, now: 200 });
|
|
186
|
+
expect(database.cancelStaleWork({
|
|
187
|
+
id: stale.id, owner: 'worker-b', sourceHash: 'source:v2', taskSpecHash: 'spec:v1', now: 210,
|
|
188
|
+
})).toBe(true);
|
|
189
|
+
expect(database.getWorkById(stale.id)).toMatchObject({ state: 'cancelled', sourceHash: 'source:v1' });
|
|
190
|
+
|
|
191
|
+
const failed = database.enqueueWork({
|
|
192
|
+
idempotencyKey: 'knowledge:dead', kind: 'knowledge.ingest', projectRoot,
|
|
193
|
+
sourceHash: 'source:dead', payload: {}, maxAttempts: 1, nextAttemptAt: 300,
|
|
194
|
+
});
|
|
195
|
+
database.leaseWork({ projectRoot, owner: 'worker-b', workItemIds: [failed.id], limit: 1, leaseMs: 100, now: 300 });
|
|
196
|
+
database.retryWork(failed.id, 'worker-b', { category: 'semantic_invalid', message: 'empty' }, 310);
|
|
197
|
+
const replay = database.replayDeadLetterWork(failed.id, {
|
|
198
|
+
idempotencyKey: 'knowledge:dead:replay:1', sourceHash: 'source:fixed', now: 320,
|
|
199
|
+
});
|
|
200
|
+
expect(replay).toMatchObject({ state: 'pending', replayOfId: failed.id, replayCount: 1, sourceHash: 'source:fixed' });
|
|
201
|
+
});
|
|
202
|
+
|
|
169
203
|
it('recovers expired leases and dead-letters exhausted work', () => {
|
|
170
204
|
database.enqueueWork({
|
|
171
205
|
idempotencyKey: 'memory:distill:retryable',
|
package/dist/database.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type AddLearningCandidateEvidenceInput, type LearningCandidateEvidenceR
|
|
|
6
6
|
import { type CreateWorkflowWorkerInput, type EnqueueWorkflowMergeInput, type TransitionWorkflowWorkerInput, type WorkflowMergeRecord, type WorkflowMergeState, type WorkflowWorkerEventRecord, type WorkflowWorkerRecord } from './workflow-workers';
|
|
7
7
|
import type { RetrievalLedgerEventRecord } from './retrieval-ledger';
|
|
8
8
|
import { type ChannelQueryPlanRecord, type TaskIntentArtifactRecord, type TerminalTransitionRecord, type ToolNameResolutionRecord, type TranscriptCheckpointRecord } from './task-semantic-control';
|
|
9
|
+
import { type TaskRuntimeEventRecord, type TaskRuntimeSnapshotRecord } from './task-runtime';
|
|
9
10
|
export interface BenchmarkReportRecord {
|
|
10
11
|
runId: string;
|
|
11
12
|
suiteId: string;
|
|
@@ -174,12 +175,17 @@ export interface HookFallbackRecord {
|
|
|
174
175
|
export declare function getGlobalDevFlowDbPath(home?: string): string;
|
|
175
176
|
export declare function openGlobalDevFlowDatabase(home?: string, options?: {
|
|
176
177
|
busyTimeoutMs?: number;
|
|
178
|
+
readonly?: boolean;
|
|
179
|
+
}): DevFlowDatabase;
|
|
180
|
+
export declare function openGlobalDevFlowReadOnlyDatabase(home?: string, options?: {
|
|
181
|
+
busyTimeoutMs?: number;
|
|
177
182
|
}): DevFlowDatabase;
|
|
178
183
|
export declare class DevFlowDatabase {
|
|
179
184
|
private db;
|
|
180
185
|
constructor(projectRoot: string, opts?: {
|
|
181
186
|
dbPath?: string;
|
|
182
187
|
busyTimeoutMs?: number;
|
|
188
|
+
readonly?: boolean;
|
|
183
189
|
});
|
|
184
190
|
private initializeSchema;
|
|
185
191
|
insertRun(run: any): void;
|
|
@@ -213,8 +219,10 @@ export declare class DevFlowDatabase {
|
|
|
213
219
|
listToolCallEventsBySession(sessionId: string): any[];
|
|
214
220
|
getToolCallEventByToolUseId(sessionId: string, toolUseId: string): {
|
|
215
221
|
eventId: string;
|
|
222
|
+
executionId: string;
|
|
216
223
|
timestamp: number;
|
|
217
224
|
duration: number;
|
|
225
|
+
input: Record<string, unknown>;
|
|
218
226
|
} | null;
|
|
219
227
|
listToolCallEventsBySessions(sessionIds: string[]): Record<string, any[]>;
|
|
220
228
|
insertSkillExecution(params: {
|
|
@@ -382,6 +390,30 @@ export declare class DevFlowDatabase {
|
|
|
382
390
|
private insertLearningActivation;
|
|
383
391
|
enqueueWork(input: EnqueueWorkInput): WorkItemRecord;
|
|
384
392
|
getWorkByIdempotencyKey(idempotencyKey: string): WorkItemRecord | null;
|
|
393
|
+
getWorkById(id: string): WorkItemRecord | null;
|
|
394
|
+
acquireProjectMutationLease(input: {
|
|
395
|
+
projectRoot: string;
|
|
396
|
+
mutationKind: string;
|
|
397
|
+
owner: string;
|
|
398
|
+
leaseMs: number;
|
|
399
|
+
sourceHash?: string;
|
|
400
|
+
now?: number;
|
|
401
|
+
}): boolean;
|
|
402
|
+
releaseProjectMutationLease(projectRoot: string, mutationKind: string, owner: string): boolean;
|
|
403
|
+
cancelStaleWork(input: {
|
|
404
|
+
id: string;
|
|
405
|
+
owner: string;
|
|
406
|
+
sourceHash?: string;
|
|
407
|
+
taskSpecHash?: string;
|
|
408
|
+
reason?: string;
|
|
409
|
+
now?: number;
|
|
410
|
+
}): boolean;
|
|
411
|
+
replayDeadLetterWork(id: string, input: {
|
|
412
|
+
idempotencyKey: string;
|
|
413
|
+
sourceHash?: string;
|
|
414
|
+
taskSpecHash?: string;
|
|
415
|
+
now?: number;
|
|
416
|
+
}): WorkItemRecord | null;
|
|
385
417
|
requestSessionClosure(input: RequestSessionClosureInput): SessionClosureRecord;
|
|
386
418
|
getSessionClosure(projectRoot: string, sessionId: string): SessionClosureRecord | null;
|
|
387
419
|
completeSessionClosure(projectRoot: string, sessionId: string, excludingWorkItemId?: string, closedAt?: number): SessionClosureRecord;
|
|
@@ -419,6 +451,7 @@ export declare class DevFlowDatabase {
|
|
|
419
451
|
upsertContextReceipt(receipt: ContextReceiptRecord): void;
|
|
420
452
|
getContextReceipt(projectRoot: string, sessionId: string, executionId: string): ContextReceiptRecord | null;
|
|
421
453
|
getActiveContextReceipt(projectRoot: string, sessionId: string, executionId?: string, now?: number): ContextReceiptRecord | null;
|
|
454
|
+
getContextReceiptForRequest(projectRoot: string, sessionId: string, requestId: string): ContextReceiptRecord | null;
|
|
422
455
|
recordContextSelectionEvent(event: ContextSelectionEventRecord): boolean;
|
|
423
456
|
appendRetrievalLedgerEvent(event: RetrievalLedgerEventRecord): boolean;
|
|
424
457
|
appendTaskIntentArtifact(record: TaskIntentArtifactRecord): TaskIntentArtifactRecord;
|
|
@@ -450,6 +483,13 @@ export declare class DevFlowDatabase {
|
|
|
450
483
|
requestId?: string;
|
|
451
484
|
}): ToolNameResolutionRecord[];
|
|
452
485
|
appendTerminalTransition(record: TerminalTransitionRecord): TerminalTransitionRecord;
|
|
486
|
+
appendTaskRuntimeEvent(event: TaskRuntimeEventRecord): TaskRuntimeEventRecord;
|
|
487
|
+
getTaskRuntimeEvent(eventId: string): TaskRuntimeEventRecord | null;
|
|
488
|
+
listTaskRuntimeEvents(projectRoot: string, sessionId: string, turnId: string): TaskRuntimeEventRecord[];
|
|
489
|
+
putTaskRuntimeSnapshot(snapshot: TaskRuntimeSnapshotRecord): TaskRuntimeSnapshotRecord;
|
|
490
|
+
appendTaskRuntimeEventAndSnapshot(event: TaskRuntimeEventRecord, snapshot: TaskRuntimeSnapshotRecord): TaskRuntimeSnapshotRecord;
|
|
491
|
+
getTaskRuntimeSnapshot(projectRoot: string, sessionId: string, turnId: string): TaskRuntimeSnapshotRecord | null;
|
|
492
|
+
deleteTaskRuntimeSnapshot(projectRoot: string, sessionId: string, turnId: string): boolean;
|
|
453
493
|
getTerminalTransition(receiptId: string): TerminalTransitionRecord | null;
|
|
454
494
|
getLatestTerminalTransition(projectRoot: string, sessionId: string, turnId: string): TerminalTransitionRecord | null;
|
|
455
495
|
appendTranscriptCheckpoint(record: TranscriptCheckpointRecord): boolean;
|
|
@@ -499,10 +539,16 @@ export declare class DevFlowDatabase {
|
|
|
499
539
|
reason?: string;
|
|
500
540
|
decidedAt?: number;
|
|
501
541
|
}): MemoryTurnRecord;
|
|
542
|
+
updateCommittedMemoryTurnProjection(input: {
|
|
543
|
+
turnId: string;
|
|
544
|
+
memoryIds: string[];
|
|
545
|
+
reason?: string;
|
|
546
|
+
}): MemoryTurnRecord;
|
|
502
547
|
skipMemoryTurn(input: {
|
|
503
548
|
turnId: string;
|
|
504
549
|
receiptId: string;
|
|
505
550
|
reason: string;
|
|
551
|
+
source?: string;
|
|
506
552
|
decidedAt?: number;
|
|
507
553
|
}): MemoryTurnRecord;
|
|
508
554
|
markMemoryTurnStopPrompted(turnId: string, promptedAt?: number): boolean;
|