@devflow-tools/database 0.17.2 → 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.
@@ -0,0 +1,169 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export interface TaskRuntimeIdentityRecord {
4
+ projectRoot: string;
5
+ projectId: string;
6
+ hostId: string;
7
+ sessionId: string;
8
+ turnId: string;
9
+ requestId: string;
10
+ executionId?: string;
11
+ }
12
+
13
+ export interface TaskRuntimeEventRecord {
14
+ schemaVersion: 'task-runtime-event.v1';
15
+ producer: string;
16
+ producerVersion: string;
17
+ eventId: string;
18
+ sequence: number;
19
+ identity: TaskRuntimeIdentityRecord;
20
+ taskSpecHash: string;
21
+ kind: string;
22
+ payload: Record<string, unknown>;
23
+ createdAt: number;
24
+ }
25
+
26
+ export type TaskRuntimeChannelRequirement = 'required' | 'optional' | 'not_required';
27
+ export type TaskRuntimeChannelStatus =
28
+ | 'satisfied' | 'intentional_abstention' | 'empty_valid' | 'degraded' | 'failed';
29
+ export type TaskRuntimeObligationState = 'pending' | 'satisfied' | 'degraded' | 'failed';
30
+
31
+ export interface TaskRuntimeChannelRecord {
32
+ requirement: TaskRuntimeChannelRequirement;
33
+ status: TaskRuntimeChannelStatus;
34
+ reasonCode: string;
35
+ evidenceIds: string[];
36
+ material: boolean;
37
+ }
38
+
39
+ export interface TaskRuntimeObligationRecord {
40
+ requirement: TaskRuntimeChannelRequirement;
41
+ state: TaskRuntimeObligationState;
42
+ receiptId?: string;
43
+ reasonCode?: string;
44
+ }
45
+
46
+ export interface TaskRuntimeSnapshotRecord {
47
+ schemaVersion: 'task-runtime-snapshot.v1';
48
+ producer: string;
49
+ producerVersion: string;
50
+ identity: TaskRuntimeIdentityRecord;
51
+ taskSpecHash: string;
52
+ lastEventSequence: number;
53
+ artifacts: {
54
+ requiredIdentityIds: string[];
55
+ resolvedIdentityIds: string[];
56
+ ambiguousIdentityIds: string[];
57
+ };
58
+ channels: Partial<Record<'code' | 'memory' | 'knowledge', TaskRuntimeChannelRecord>>;
59
+ contextReceiptId?: string;
60
+ action: TaskRuntimeObligationRecord;
61
+ verification: TaskRuntimeObligationRecord;
62
+ memory: TaskRuntimeObligationRecord;
63
+ durableWorkIds: string[];
64
+ terminal: 'running' | 'retry_pending' | 'completed' | 'completed_with_degradation' | 'failed';
65
+ runtimeHealth: 'healthy' | 'degraded' | 'unavailable';
66
+ snapshotHash: string;
67
+ updatedAt: number;
68
+ }
69
+
70
+ export function stableTaskRuntimeJson(value: unknown): string {
71
+ return JSON.stringify(canonicalize(value));
72
+ }
73
+
74
+ export function stableTaskRuntimeHash(value: unknown): string {
75
+ return createHash('sha256').update(stableTaskRuntimeJson(value)).digest('hex');
76
+ }
77
+
78
+ export function mapTaskRuntimeEventRow(row: Record<string, unknown>): TaskRuntimeEventRecord {
79
+ return {
80
+ schemaVersion: requiredLiteral(row.schema_version, 'task-runtime-event.v1'),
81
+ producer: requiredString(row.producer, 'producer'),
82
+ producerVersion: requiredString(row.producer_version, 'producer_version'),
83
+ eventId: requiredString(row.event_id, 'event_id'),
84
+ sequence: requiredPositiveInteger(row.sequence, 'sequence'),
85
+ identity: mapIdentity(row),
86
+ taskSpecHash: requiredString(row.task_spec_hash, 'task_spec_hash'),
87
+ kind: requiredString(row.kind, 'kind'),
88
+ payload: parseObject(row.payload_json, 'payload_json'),
89
+ createdAt: requiredNonNegativeInteger(row.created_at, 'created_at'),
90
+ };
91
+ }
92
+
93
+ export function mapTaskRuntimeSnapshotRow(row: Record<string, unknown>): TaskRuntimeSnapshotRecord {
94
+ const snapshot = parseObject(row.snapshot_json, 'snapshot_json') as unknown as TaskRuntimeSnapshotRecord;
95
+ if (snapshot.schemaVersion !== 'task-runtime-snapshot.v1') {
96
+ throw new Error(`TASK_RUNTIME_SCHEMA_UNSUPPORTED:${String(snapshot.schemaVersion)}`);
97
+ }
98
+ if (snapshot.snapshotHash !== requiredString(row.snapshot_hash, 'snapshot_hash')) {
99
+ throw new Error('TASK_RUNTIME_SNAPSHOT_HASH_MISMATCH');
100
+ }
101
+ const { snapshotHash: _snapshotHash, ...body } = snapshot;
102
+ if (stableTaskRuntimeHash(body) !== snapshot.snapshotHash) {
103
+ throw new Error('TASK_RUNTIME_SNAPSHOT_HASH_MISMATCH');
104
+ }
105
+ return snapshot;
106
+ }
107
+
108
+ function mapIdentity(row: Record<string, unknown>): TaskRuntimeIdentityRecord {
109
+ return {
110
+ projectRoot: requiredString(row.project_root, 'project_root'),
111
+ projectId: requiredString(row.project_id, 'project_id'),
112
+ hostId: requiredString(row.host_id, 'host_id'),
113
+ sessionId: requiredString(row.session_id, 'session_id'),
114
+ turnId: requiredString(row.turn_id, 'turn_id'),
115
+ requestId: requiredString(row.request_id, 'request_id'),
116
+ ...(optionalString(row.execution_id) ? { executionId: optionalString(row.execution_id) } : {}),
117
+ };
118
+ }
119
+
120
+ function requiredLiteral<T extends string>(value: unknown, literal: T): T {
121
+ if (value !== literal) throw new Error(`TASK_RUNTIME_SCHEMA_UNSUPPORTED:${String(value)}`);
122
+ return literal;
123
+ }
124
+
125
+ function requiredString(value: unknown, field: string): string {
126
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`TASK_RUNTIME_ROW_INVALID:${field}`);
127
+ return value;
128
+ }
129
+
130
+ function optionalString(value: unknown): string | undefined {
131
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
132
+ }
133
+
134
+ function requiredPositiveInteger(value: unknown, field: string): number {
135
+ const result = Number(value);
136
+ if (!Number.isSafeInteger(result) || result < 1) throw new Error(`TASK_RUNTIME_ROW_INVALID:${field}`);
137
+ return result;
138
+ }
139
+
140
+ function requiredNonNegativeInteger(value: unknown, field: string): number {
141
+ const result = Number(value);
142
+ if (!Number.isSafeInteger(result) || result < 0) throw new Error(`TASK_RUNTIME_ROW_INVALID:${field}`);
143
+ return result;
144
+ }
145
+
146
+ function parseObject(value: unknown, field: string): Record<string, unknown> {
147
+ if (typeof value !== 'string') throw new Error(`TASK_RUNTIME_ROW_INVALID:${field}`);
148
+ try {
149
+ const parsed = JSON.parse(value) as unknown;
150
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('not object');
151
+ return parsed as Record<string, unknown>;
152
+ } catch {
153
+ throw new Error(`TASK_RUNTIME_ROW_INVALID:${field}`);
154
+ }
155
+ }
156
+
157
+ function canonicalize(value: unknown): unknown {
158
+ if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
159
+ if (typeof value === 'number') {
160
+ if (!Number.isFinite(value) || Object.is(value, -0)) throw new Error('TASK_RUNTIME_JSON_INVALID');
161
+ return value;
162
+ }
163
+ if (Array.isArray(value)) return value.map(canonicalize);
164
+ if (!value || typeof value !== 'object' || Object.getPrototypeOf(value) !== Object.prototype) {
165
+ throw new Error('TASK_RUNTIME_JSON_INVALID');
166
+ }
167
+ return Object.fromEntries(Object.keys(value as Record<string, unknown>).sort()
168
+ .map(key => [key, canonicalize((value as Record<string, unknown>)[key])]));
169
+ }
package/src/work-queue.ts CHANGED
@@ -4,6 +4,8 @@ export type WorkKind =
4
4
  | 'memory.turn_capture'
5
5
  | 'memory.turn_distill'
6
6
  | 'memory.vector_backfill'
7
+ | 'knowledge.ingest'
8
+ | 'knowledge.index_refresh'
7
9
  | 'context.prefetch'
8
10
  | 'verification.policy'
9
11
  | 'learning.analyze_session'
@@ -16,7 +18,7 @@ export type WorkKind =
16
18
  | 'session.finalize'
17
19
  | 'telemetry.reconcile';
18
20
 
19
- export type WorkState = 'pending' | 'leased' | 'completed' | 'failed' | 'dead_letter';
21
+ export type WorkState = 'pending' | 'leased' | 'completed' | 'failed' | 'dead_letter' | 'cancelled';
20
22
 
21
23
  export interface WorkError {
22
24
  category: string;
@@ -30,6 +32,8 @@ export interface WorkItemRecord {
30
32
  projectRoot: string;
31
33
  sessionId?: string;
32
34
  turnId?: string;
35
+ sourceHash?: string;
36
+ taskSpecHash?: string;
33
37
  payload: unknown;
34
38
  state: WorkState;
35
39
  attempts: number;
@@ -42,6 +46,10 @@ export interface WorkItemRecord {
42
46
  createdAt: number;
43
47
  updatedAt: number;
44
48
  completedAt?: number;
49
+ cancelledAt?: number;
50
+ cancellationReason?: string;
51
+ replayOfId?: string;
52
+ replayCount: number;
45
53
  }
46
54
 
47
55
  export interface EnqueueWorkInput {
@@ -50,11 +58,22 @@ export interface EnqueueWorkInput {
50
58
  projectRoot: string;
51
59
  sessionId?: string;
52
60
  turnId?: string;
61
+ sourceHash?: string;
62
+ taskSpecHash?: string;
53
63
  payload: unknown;
54
64
  maxAttempts?: number;
55
65
  nextAttemptAt?: number;
56
66
  }
57
67
 
68
+ export interface ProjectMutationLeaseRecord {
69
+ projectRoot: string;
70
+ mutationKind: string;
71
+ owner: string;
72
+ leaseExpiresAt: number;
73
+ sourceHash?: string;
74
+ updatedAt: number;
75
+ }
76
+
58
77
  export interface LeaseWorkInput {
59
78
  projectRoot: string;
60
79
  owner: string;