@devflow-tools/database 0.16.34 → 0.17.1
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 +25 -0
- package/dist/database.d.ts +39 -0
- package/dist/database.js +349 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/task-semantic-control.d.ts +79 -0
- package/dist/task-semantic-control.js +196 -0
- package/package.json +1 -1
- package/src/database.ts +433 -0
- package/src/index.ts +18 -0
- package/src/task-semantic-control.ts +270 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stableSemanticJson = stableSemanticJson;
|
|
4
|
+
exports.mapTaskIntentArtifactRow = mapTaskIntentArtifactRow;
|
|
5
|
+
exports.mapChannelQueryPlanRow = mapChannelQueryPlanRow;
|
|
6
|
+
exports.mapToolNameResolutionRow = mapToolNameResolutionRow;
|
|
7
|
+
exports.mapTerminalTransitionRow = mapTerminalTransitionRow;
|
|
8
|
+
exports.mapTranscriptCheckpointRow = mapTranscriptCheckpointRow;
|
|
9
|
+
exports.assertTaskIdentity = assertTaskIdentity;
|
|
10
|
+
function stableSemanticJson(value) {
|
|
11
|
+
return JSON.stringify(canonicalize(value));
|
|
12
|
+
}
|
|
13
|
+
function mapTaskIntentArtifactRow(row) {
|
|
14
|
+
return {
|
|
15
|
+
...mapTaskIdentityRow(row),
|
|
16
|
+
version: requiredPositiveInteger(row.version, 'version'),
|
|
17
|
+
supersedesHash: optionalString(row.supersedes_hash),
|
|
18
|
+
rawPrompt: requiredString(row.raw_prompt, 'raw_prompt'),
|
|
19
|
+
normalizedPrompt: requiredString(row.normalized_prompt, 'normalized_prompt'),
|
|
20
|
+
command: optionalString(row.command),
|
|
21
|
+
slashArgs: parseStringArray(row.slash_args_json, 'slash_args_json'),
|
|
22
|
+
activeSkill: optionalString(row.active_skill),
|
|
23
|
+
intent: requiredString(row.intent, 'intent'),
|
|
24
|
+
action: requiredString(row.action, 'action'),
|
|
25
|
+
entities: parseStringArray(row.entities_json, 'entities_json'),
|
|
26
|
+
targetAnchors: parseStringArray(row.target_anchors_json, 'target_anchors_json'),
|
|
27
|
+
policyConstraints: parseStringArray(row.policy_constraints_json, 'policy_constraints_json'),
|
|
28
|
+
classificationEvidence: parseObjectArray(row.classification_evidence_json, 'classification_evidence_json'),
|
|
29
|
+
sourceEventIds: parseStringArray(row.source_event_ids_json, 'source_event_ids_json'),
|
|
30
|
+
sourceHash: requiredString(row.source_hash, 'source_hash'),
|
|
31
|
+
artifactHash: requiredString(row.artifact_hash, 'artifact_hash'),
|
|
32
|
+
createdAt: requiredNonNegativeInteger(row.created_at, 'created_at'),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function mapChannelQueryPlanRow(row) {
|
|
36
|
+
const generatedBy = requiredString(row.generated_by, 'generated_by');
|
|
37
|
+
if (generatedBy !== 'deterministic' && generatedBy !== 'host_semantic' && generatedBy !== 'hybrid') {
|
|
38
|
+
throw new Error(`Invalid persisted generated_by: ${generatedBy}`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
...mapTaskIdentityRow(row),
|
|
42
|
+
sourceIntentHash: requiredString(row.source_intent_hash, 'source_intent_hash'),
|
|
43
|
+
planHash: requiredString(row.plan_hash, 'plan_hash'),
|
|
44
|
+
code: parseObjectArray(row.code_queries_json, 'code_queries_json'),
|
|
45
|
+
memory: parseObjectArray(row.memory_queries_json, 'memory_queries_json'),
|
|
46
|
+
knowledge: parseObjectArray(row.knowledge_queries_json, 'knowledge_queries_json'),
|
|
47
|
+
generatedBy,
|
|
48
|
+
degradation: parseOptionalObject(row.degradation_json, 'degradation_json'),
|
|
49
|
+
createdAt: requiredNonNegativeInteger(row.created_at, 'created_at'),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function mapToolNameResolutionRow(row) {
|
|
53
|
+
const status = requiredString(row.status, 'status');
|
|
54
|
+
if (status !== 'canonical' && status !== 'alias' && status !== 'unsupported') {
|
|
55
|
+
throw new Error(`Invalid persisted tool resolution status: ${status}`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...mapTaskIdentityRow(row),
|
|
59
|
+
id: requiredString(row.id, 'id'),
|
|
60
|
+
sourceHash: requiredString(row.source_hash, 'source_hash'),
|
|
61
|
+
requestedName: requiredString(row.requested_name, 'requested_name'),
|
|
62
|
+
canonicalName: optionalString(row.canonical_name),
|
|
63
|
+
projectedName: optionalString(row.projected_name),
|
|
64
|
+
status,
|
|
65
|
+
attempt: requiredNonNegativeInteger(row.attempt, 'attempt'),
|
|
66
|
+
capabilityMechanism: optionalString(row.capability_mechanism),
|
|
67
|
+
reason: optionalString(row.reason),
|
|
68
|
+
createdAt: requiredNonNegativeInteger(row.created_at, 'created_at'),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function mapTerminalTransitionRow(row) {
|
|
72
|
+
return {
|
|
73
|
+
...mapTaskIdentityRow(row),
|
|
74
|
+
sequence: requiredPositiveInteger(row.sequence, 'sequence'),
|
|
75
|
+
fromState: optionalString(row.from_state),
|
|
76
|
+
toState: requiredString(row.to_state, 'to_state'),
|
|
77
|
+
receiptId: requiredString(row.receipt_id, 'receipt_id'),
|
|
78
|
+
sourceReceiptId: optionalString(row.source_receipt_id),
|
|
79
|
+
reason: requiredString(row.reason, 'reason'),
|
|
80
|
+
payload: parseObject(row.payload_json, 'payload_json'),
|
|
81
|
+
createdAt: requiredNonNegativeInteger(row.created_at, 'created_at'),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function mapTranscriptCheckpointRow(row) {
|
|
85
|
+
return {
|
|
86
|
+
id: requiredString(row.id, 'id'),
|
|
87
|
+
projectRoot: requiredString(row.project_root, 'project_root'),
|
|
88
|
+
hostId: requiredString(row.host_id, 'host_id'),
|
|
89
|
+
sessionId: requiredString(row.session_id, 'session_id'),
|
|
90
|
+
sourcePath: requiredString(row.source_path, 'source_path'),
|
|
91
|
+
sourceHash: requiredString(row.source_hash, 'source_hash'),
|
|
92
|
+
byteOffset: requiredNonNegativeInteger(row.byte_offset, 'byte_offset'),
|
|
93
|
+
eventCount: requiredNonNegativeInteger(row.event_count, 'event_count'),
|
|
94
|
+
createdAt: requiredNonNegativeInteger(row.created_at, 'created_at'),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function assertTaskIdentity(record) {
|
|
98
|
+
for (const [field, value] of Object.entries({
|
|
99
|
+
projectRoot: record.projectRoot,
|
|
100
|
+
projectId: record.projectId,
|
|
101
|
+
hostId: record.hostId,
|
|
102
|
+
sessionId: record.sessionId,
|
|
103
|
+
turnId: record.turnId,
|
|
104
|
+
requestId: record.requestId,
|
|
105
|
+
})) {
|
|
106
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
107
|
+
throw new Error(`TASK_IDENTITY_INVALID:${field}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (record.executionId !== undefined && record.executionId.trim().length === 0) {
|
|
111
|
+
throw new Error('TASK_IDENTITY_INVALID:executionId');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function mapTaskIdentityRow(row) {
|
|
115
|
+
return {
|
|
116
|
+
projectRoot: requiredString(row.project_root, 'project_root'),
|
|
117
|
+
projectId: requiredString(row.project_id, 'project_id'),
|
|
118
|
+
hostId: requiredString(row.host_id, 'host_id'),
|
|
119
|
+
sessionId: requiredString(row.session_id, 'session_id'),
|
|
120
|
+
turnId: requiredString(row.turn_id, 'turn_id'),
|
|
121
|
+
requestId: requiredString(row.request_id, 'request_id'),
|
|
122
|
+
executionId: optionalString(row.execution_id),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function canonicalize(value) {
|
|
126
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
127
|
+
return value;
|
|
128
|
+
if (typeof value === 'number') {
|
|
129
|
+
if (!Number.isFinite(value) || Object.is(value, -0))
|
|
130
|
+
throw new Error('Semantic control evidence must be lossless JSON');
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(value))
|
|
134
|
+
return value.map(canonicalize);
|
|
135
|
+
if (!value || typeof value !== 'object' || Object.getPrototypeOf(value) !== Object.prototype) {
|
|
136
|
+
throw new Error('Semantic control evidence must be plain JSON');
|
|
137
|
+
}
|
|
138
|
+
const record = value;
|
|
139
|
+
return Object.fromEntries(Object.keys(record).sort().map(key => [key, canonicalize(record[key])]));
|
|
140
|
+
}
|
|
141
|
+
function parseJson(value, field) {
|
|
142
|
+
if (typeof value !== 'string')
|
|
143
|
+
throw new Error(`Invalid persisted ${field}: expected JSON text`);
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(value);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
throw new Error(`Invalid persisted ${field}: malformed JSON`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function parseStringArray(value, field) {
|
|
152
|
+
const parsed = parseJson(value, field);
|
|
153
|
+
if (!Array.isArray(parsed) || parsed.some(item => typeof item !== 'string')) {
|
|
154
|
+
throw new Error(`Invalid persisted ${field}: expected string array`);
|
|
155
|
+
}
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
function parseObjectArray(value, field) {
|
|
159
|
+
const parsed = parseJson(value, field);
|
|
160
|
+
if (!Array.isArray(parsed) || parsed.some(item => !item || typeof item !== 'object' || Array.isArray(item))) {
|
|
161
|
+
throw new Error(`Invalid persisted ${field}: expected object array`);
|
|
162
|
+
}
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
function parseObject(value, field) {
|
|
166
|
+
const parsed = parseJson(value, field);
|
|
167
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
168
|
+
throw new Error(`Invalid persisted ${field}: expected object`);
|
|
169
|
+
}
|
|
170
|
+
return parsed;
|
|
171
|
+
}
|
|
172
|
+
function parseOptionalObject(value, field) {
|
|
173
|
+
if (value === null || value === undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
return parseObject(value, field);
|
|
176
|
+
}
|
|
177
|
+
function requiredString(value, field) {
|
|
178
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
179
|
+
throw new Error(`Invalid persisted ${field}`);
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
function optionalString(value) {
|
|
183
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
184
|
+
}
|
|
185
|
+
function requiredNonNegativeInteger(value, field) {
|
|
186
|
+
const result = Number(value);
|
|
187
|
+
if (!Number.isSafeInteger(result) || result < 0)
|
|
188
|
+
throw new Error(`Invalid persisted ${field}`);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
function requiredPositiveInteger(value, field) {
|
|
192
|
+
const result = requiredNonNegativeInteger(value, field);
|
|
193
|
+
if (result === 0)
|
|
194
|
+
throw new Error(`Invalid persisted ${field}`);
|
|
195
|
+
return result;
|
|
196
|
+
}
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -70,6 +70,20 @@ import {
|
|
|
70
70
|
type WorkflowWorkerRecord,
|
|
71
71
|
} from './workflow-workers';
|
|
72
72
|
import type { RetrievalLedgerEventRecord } from './retrieval-ledger';
|
|
73
|
+
import {
|
|
74
|
+
assertTaskIdentity,
|
|
75
|
+
mapChannelQueryPlanRow,
|
|
76
|
+
mapTaskIntentArtifactRow,
|
|
77
|
+
mapTerminalTransitionRow,
|
|
78
|
+
mapToolNameResolutionRow,
|
|
79
|
+
mapTranscriptCheckpointRow,
|
|
80
|
+
stableSemanticJson,
|
|
81
|
+
type ChannelQueryPlanRecord,
|
|
82
|
+
type TaskIntentArtifactRecord,
|
|
83
|
+
type TerminalTransitionRecord,
|
|
84
|
+
type ToolNameResolutionRecord,
|
|
85
|
+
type TranscriptCheckpointRecord,
|
|
86
|
+
} from './task-semantic-control';
|
|
73
87
|
|
|
74
88
|
export interface BenchmarkReportRecord {
|
|
75
89
|
runId: string;
|
|
@@ -567,6 +581,119 @@ export class DevFlowDatabase {
|
|
|
567
581
|
project_root, session_id, execution_id, request_id, context_receipt, source_type, source_id, created_at
|
|
568
582
|
);
|
|
569
583
|
|
|
584
|
+
CREATE TABLE IF NOT EXISTS devflow_task_intent_artifacts (
|
|
585
|
+
artifact_hash TEXT PRIMARY KEY,
|
|
586
|
+
project_root TEXT NOT NULL,
|
|
587
|
+
project_id TEXT NOT NULL,
|
|
588
|
+
host_id TEXT NOT NULL,
|
|
589
|
+
session_id TEXT NOT NULL,
|
|
590
|
+
turn_id TEXT NOT NULL,
|
|
591
|
+
request_id TEXT NOT NULL,
|
|
592
|
+
execution_id TEXT,
|
|
593
|
+
version INTEGER NOT NULL CHECK(version > 0),
|
|
594
|
+
supersedes_hash TEXT,
|
|
595
|
+
raw_prompt TEXT NOT NULL,
|
|
596
|
+
normalized_prompt TEXT NOT NULL,
|
|
597
|
+
command TEXT,
|
|
598
|
+
slash_args_json TEXT NOT NULL DEFAULT '[]',
|
|
599
|
+
active_skill TEXT,
|
|
600
|
+
intent TEXT NOT NULL,
|
|
601
|
+
action TEXT NOT NULL,
|
|
602
|
+
entities_json TEXT NOT NULL DEFAULT '[]',
|
|
603
|
+
target_anchors_json TEXT NOT NULL DEFAULT '[]',
|
|
604
|
+
policy_constraints_json TEXT NOT NULL DEFAULT '[]',
|
|
605
|
+
classification_evidence_json TEXT NOT NULL DEFAULT '[]',
|
|
606
|
+
source_event_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
607
|
+
source_hash TEXT NOT NULL,
|
|
608
|
+
created_at INTEGER NOT NULL,
|
|
609
|
+
UNIQUE(project_root, session_id, turn_id, version),
|
|
610
|
+
UNIQUE(project_root, session_id, request_id, version)
|
|
611
|
+
);
|
|
612
|
+
CREATE INDEX IF NOT EXISTS idx_task_intent_active
|
|
613
|
+
ON devflow_task_intent_artifacts(project_root, session_id, turn_id, version DESC);
|
|
614
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_intent_source
|
|
615
|
+
ON devflow_task_intent_artifacts(project_root, session_id, source_hash, version);
|
|
616
|
+
|
|
617
|
+
CREATE TABLE IF NOT EXISTS devflow_channel_query_plans (
|
|
618
|
+
plan_hash TEXT PRIMARY KEY,
|
|
619
|
+
source_intent_hash TEXT NOT NULL,
|
|
620
|
+
project_root TEXT NOT NULL,
|
|
621
|
+
project_id TEXT NOT NULL,
|
|
622
|
+
host_id TEXT NOT NULL,
|
|
623
|
+
session_id TEXT NOT NULL,
|
|
624
|
+
turn_id TEXT NOT NULL,
|
|
625
|
+
request_id TEXT NOT NULL,
|
|
626
|
+
execution_id TEXT,
|
|
627
|
+
code_queries_json TEXT NOT NULL DEFAULT '[]',
|
|
628
|
+
memory_queries_json TEXT NOT NULL DEFAULT '[]',
|
|
629
|
+
knowledge_queries_json TEXT NOT NULL DEFAULT '[]',
|
|
630
|
+
generated_by TEXT NOT NULL
|
|
631
|
+
CHECK(generated_by IN ('deterministic', 'host_semantic', 'hybrid')),
|
|
632
|
+
degradation_json TEXT,
|
|
633
|
+
created_at INTEGER NOT NULL,
|
|
634
|
+
UNIQUE(project_root, session_id, turn_id, source_intent_hash)
|
|
635
|
+
);
|
|
636
|
+
CREATE INDEX IF NOT EXISTS idx_channel_query_plan_request
|
|
637
|
+
ON devflow_channel_query_plans(project_root, session_id, request_id, created_at DESC);
|
|
638
|
+
|
|
639
|
+
CREATE TABLE IF NOT EXISTS devflow_tool_name_resolutions (
|
|
640
|
+
id TEXT PRIMARY KEY,
|
|
641
|
+
source_hash TEXT NOT NULL UNIQUE,
|
|
642
|
+
project_root TEXT NOT NULL,
|
|
643
|
+
project_id TEXT NOT NULL,
|
|
644
|
+
host_id TEXT NOT NULL,
|
|
645
|
+
session_id TEXT NOT NULL,
|
|
646
|
+
turn_id TEXT NOT NULL,
|
|
647
|
+
request_id TEXT NOT NULL,
|
|
648
|
+
execution_id TEXT,
|
|
649
|
+
requested_name TEXT NOT NULL,
|
|
650
|
+
canonical_name TEXT,
|
|
651
|
+
projected_name TEXT,
|
|
652
|
+
status TEXT NOT NULL CHECK(status IN ('canonical', 'alias', 'unsupported')),
|
|
653
|
+
attempt INTEGER NOT NULL CHECK(attempt >= 0),
|
|
654
|
+
capability_mechanism TEXT,
|
|
655
|
+
reason TEXT,
|
|
656
|
+
created_at INTEGER NOT NULL
|
|
657
|
+
);
|
|
658
|
+
CREATE INDEX IF NOT EXISTS idx_tool_resolution_request
|
|
659
|
+
ON devflow_tool_name_resolutions(project_root, session_id, request_id, created_at);
|
|
660
|
+
|
|
661
|
+
CREATE TABLE IF NOT EXISTS devflow_terminal_transitions (
|
|
662
|
+
receipt_id TEXT PRIMARY KEY,
|
|
663
|
+
project_root TEXT NOT NULL,
|
|
664
|
+
project_id TEXT NOT NULL,
|
|
665
|
+
host_id TEXT NOT NULL,
|
|
666
|
+
session_id TEXT NOT NULL,
|
|
667
|
+
turn_id TEXT NOT NULL,
|
|
668
|
+
request_id TEXT NOT NULL,
|
|
669
|
+
execution_id TEXT,
|
|
670
|
+
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
671
|
+
from_state TEXT,
|
|
672
|
+
to_state TEXT NOT NULL,
|
|
673
|
+
source_receipt_id TEXT,
|
|
674
|
+
reason TEXT NOT NULL,
|
|
675
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
676
|
+
created_at INTEGER NOT NULL,
|
|
677
|
+
UNIQUE(project_root, session_id, turn_id, sequence)
|
|
678
|
+
);
|
|
679
|
+
CREATE INDEX IF NOT EXISTS idx_terminal_transition_turn
|
|
680
|
+
ON devflow_terminal_transitions(project_root, session_id, turn_id, sequence);
|
|
681
|
+
|
|
682
|
+
CREATE TABLE IF NOT EXISTS devflow_transcript_checkpoints (
|
|
683
|
+
id TEXT PRIMARY KEY,
|
|
684
|
+
project_root TEXT NOT NULL,
|
|
685
|
+
host_id TEXT NOT NULL,
|
|
686
|
+
session_id TEXT NOT NULL,
|
|
687
|
+
source_path TEXT NOT NULL,
|
|
688
|
+
source_hash TEXT NOT NULL,
|
|
689
|
+
byte_offset INTEGER NOT NULL CHECK(byte_offset >= 0),
|
|
690
|
+
event_count INTEGER NOT NULL CHECK(event_count >= 0),
|
|
691
|
+
created_at INTEGER NOT NULL,
|
|
692
|
+
UNIQUE(project_root, host_id, session_id, source_path, source_hash)
|
|
693
|
+
);
|
|
694
|
+
CREATE INDEX IF NOT EXISTS idx_transcript_checkpoint_latest
|
|
695
|
+
ON devflow_transcript_checkpoints(project_root, host_id, session_id, source_path, created_at DESC);
|
|
696
|
+
|
|
570
697
|
CREATE TABLE IF NOT EXISTS devflow_policy_verification_baselines (
|
|
571
698
|
project_root TEXT NOT NULL,
|
|
572
699
|
kind TEXT NOT NULL,
|
|
@@ -3685,6 +3812,293 @@ export class DevFlowDatabase {
|
|
|
3685
3812
|
).changes > 0;
|
|
3686
3813
|
}
|
|
3687
3814
|
|
|
3815
|
+
appendTaskIntentArtifact(record: TaskIntentArtifactRecord): TaskIntentArtifactRecord {
|
|
3816
|
+
assertTaskIdentity(record);
|
|
3817
|
+
if (!Number.isSafeInteger(record.version) || record.version <= 0) {
|
|
3818
|
+
throw new Error('TASK_INTENT_INVALID_VERSION');
|
|
3819
|
+
}
|
|
3820
|
+
const existingByHash = this.getTaskIntentArtifact(record.artifactHash);
|
|
3821
|
+
if (existingByHash) {
|
|
3822
|
+
if (stableSemanticJson(existingByHash) !== stableSemanticJson(record)) {
|
|
3823
|
+
throw new Error(`TASK_INTENT_HASH_CONFLICT:${record.artifactHash}`);
|
|
3824
|
+
}
|
|
3825
|
+
return existingByHash;
|
|
3826
|
+
}
|
|
3827
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
3828
|
+
try {
|
|
3829
|
+
const conflicting = this.db.prepare(`
|
|
3830
|
+
SELECT * FROM devflow_task_intent_artifacts
|
|
3831
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND version = ?
|
|
3832
|
+
`).get(record.projectRoot, record.sessionId, record.turnId, record.version) as Record<string, unknown> | undefined;
|
|
3833
|
+
if (conflicting) throw new Error(`TASK_INTENT_VERSION_CONFLICT:${record.turnId}:${record.version}`);
|
|
3834
|
+
if (record.supersedesHash && !this.getTaskIntentArtifact(record.supersedesHash)) {
|
|
3835
|
+
throw new Error(`TASK_INTENT_SUPERSEDED_NOT_FOUND:${record.supersedesHash}`);
|
|
3836
|
+
}
|
|
3837
|
+
this.db.prepare(`
|
|
3838
|
+
INSERT INTO devflow_task_intent_artifacts (
|
|
3839
|
+
artifact_hash, project_root, project_id, host_id, session_id, turn_id, request_id,
|
|
3840
|
+
execution_id, version, supersedes_hash, raw_prompt, normalized_prompt, command,
|
|
3841
|
+
slash_args_json, active_skill, intent, action, entities_json, target_anchors_json,
|
|
3842
|
+
policy_constraints_json, classification_evidence_json, source_event_ids_json,
|
|
3843
|
+
source_hash, created_at
|
|
3844
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3845
|
+
`).run(
|
|
3846
|
+
record.artifactHash, record.projectRoot, record.projectId, record.hostId,
|
|
3847
|
+
record.sessionId, record.turnId, record.requestId, record.executionId ?? null,
|
|
3848
|
+
record.version, record.supersedesHash ?? null, record.rawPrompt, record.normalizedPrompt,
|
|
3849
|
+
record.command ?? null, stableSemanticJson(record.slashArgs), record.activeSkill ?? null,
|
|
3850
|
+
record.intent, record.action, stableSemanticJson(record.entities),
|
|
3851
|
+
stableSemanticJson(record.targetAnchors), stableSemanticJson(record.policyConstraints),
|
|
3852
|
+
stableSemanticJson(record.classificationEvidence), stableSemanticJson(record.sourceEventIds),
|
|
3853
|
+
record.sourceHash, record.createdAt,
|
|
3854
|
+
);
|
|
3855
|
+
const created = this.getTaskIntentArtifact(record.artifactHash)!;
|
|
3856
|
+
this.db.exec('COMMIT');
|
|
3857
|
+
return created;
|
|
3858
|
+
} catch (error) {
|
|
3859
|
+
try { this.db.exec('ROLLBACK'); } catch {}
|
|
3860
|
+
throw error;
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
|
|
3864
|
+
getTaskIntentArtifact(artifactHash: string): TaskIntentArtifactRecord | null {
|
|
3865
|
+
const row = this.db.prepare(`
|
|
3866
|
+
SELECT * FROM devflow_task_intent_artifacts WHERE artifact_hash = ?
|
|
3867
|
+
`).get(artifactHash) as Record<string, unknown> | undefined;
|
|
3868
|
+
return row ? mapTaskIntentArtifactRow(row) : null;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
getLatestTaskIntentArtifact(input: {
|
|
3872
|
+
projectRoot: string;
|
|
3873
|
+
sessionId: string;
|
|
3874
|
+
turnId?: string;
|
|
3875
|
+
requestId?: string;
|
|
3876
|
+
}): TaskIntentArtifactRecord | null {
|
|
3877
|
+
if (!input.turnId && !input.requestId) throw new Error('TASK_INTENT_LOOKUP_IDENTITY_REQUIRED');
|
|
3878
|
+
const predicates = ['project_root = ?', 'session_id = ?'];
|
|
3879
|
+
const params: unknown[] = [input.projectRoot, input.sessionId];
|
|
3880
|
+
if (input.turnId) { predicates.push('turn_id = ?'); params.push(input.turnId); }
|
|
3881
|
+
if (input.requestId) { predicates.push('request_id = ?'); params.push(input.requestId); }
|
|
3882
|
+
const row = this.db.prepare(`
|
|
3883
|
+
SELECT * FROM devflow_task_intent_artifacts
|
|
3884
|
+
WHERE ${predicates.join(' AND ')} ORDER BY version DESC, created_at DESC LIMIT 1
|
|
3885
|
+
`).get(...params) as Record<string, unknown> | undefined;
|
|
3886
|
+
return row ? mapTaskIntentArtifactRow(row) : null;
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
getTaskIntentArtifactBySource(input: {
|
|
3890
|
+
projectRoot: string;
|
|
3891
|
+
sessionId: string;
|
|
3892
|
+
sourceHash: string;
|
|
3893
|
+
}): TaskIntentArtifactRecord | null {
|
|
3894
|
+
const row = this.db.prepare(`
|
|
3895
|
+
SELECT * FROM devflow_task_intent_artifacts
|
|
3896
|
+
WHERE project_root = ? AND session_id = ? AND source_hash = ?
|
|
3897
|
+
ORDER BY version DESC, created_at DESC LIMIT 1
|
|
3898
|
+
`).get(input.projectRoot, input.sessionId, input.sourceHash) as Record<string, unknown> | undefined;
|
|
3899
|
+
return row ? mapTaskIntentArtifactRow(row) : null;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
listLatestTaskIntentArtifacts(projectRoot: string, sessionId: string): TaskIntentArtifactRecord[] {
|
|
3903
|
+
return (this.db.prepare(`
|
|
3904
|
+
SELECT artifact.* FROM devflow_task_intent_artifacts artifact
|
|
3905
|
+
JOIN (
|
|
3906
|
+
SELECT turn_id, MAX(version) AS version
|
|
3907
|
+
FROM devflow_task_intent_artifacts
|
|
3908
|
+
WHERE project_root = ? AND session_id = ? GROUP BY turn_id
|
|
3909
|
+
) latest ON latest.turn_id = artifact.turn_id AND latest.version = artifact.version
|
|
3910
|
+
WHERE artifact.project_root = ? AND artifact.session_id = ?
|
|
3911
|
+
ORDER BY artifact.created_at ASC, artifact.turn_id ASC
|
|
3912
|
+
`).all(projectRoot, sessionId, projectRoot, sessionId) as Array<Record<string, unknown>>)
|
|
3913
|
+
.map(mapTaskIntentArtifactRow);
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
appendChannelQueryPlan(record: ChannelQueryPlanRecord): ChannelQueryPlanRecord {
|
|
3917
|
+
assertTaskIdentity(record);
|
|
3918
|
+
const source = this.getTaskIntentArtifact(record.sourceIntentHash);
|
|
3919
|
+
if (!source) throw new Error(`QUERY_PLAN_INTENT_NOT_FOUND:${record.sourceIntentHash}`);
|
|
3920
|
+
if (!sameTaskIdentity(source, record)) throw new Error('QUERY_PLAN_IDENTITY_MISMATCH');
|
|
3921
|
+
const existing = this.getChannelQueryPlan(record.planHash);
|
|
3922
|
+
if (existing) {
|
|
3923
|
+
if (stableSemanticJson(existing) !== stableSemanticJson(record)) {
|
|
3924
|
+
throw new Error(`QUERY_PLAN_HASH_CONFLICT:${record.planHash}`);
|
|
3925
|
+
}
|
|
3926
|
+
return existing;
|
|
3927
|
+
}
|
|
3928
|
+
try {
|
|
3929
|
+
this.db.prepare(`
|
|
3930
|
+
INSERT INTO devflow_channel_query_plans (
|
|
3931
|
+
plan_hash, source_intent_hash, project_root, project_id, host_id, session_id,
|
|
3932
|
+
turn_id, request_id, execution_id, code_queries_json, memory_queries_json,
|
|
3933
|
+
knowledge_queries_json, generated_by, degradation_json, created_at
|
|
3934
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3935
|
+
`).run(
|
|
3936
|
+
record.planHash, record.sourceIntentHash, record.projectRoot, record.projectId,
|
|
3937
|
+
record.hostId, record.sessionId, record.turnId, record.requestId,
|
|
3938
|
+
record.executionId ?? null, stableSemanticJson(record.code),
|
|
3939
|
+
stableSemanticJson(record.memory), stableSemanticJson(record.knowledge),
|
|
3940
|
+
record.generatedBy, record.degradation ? stableSemanticJson(record.degradation) : null,
|
|
3941
|
+
record.createdAt,
|
|
3942
|
+
);
|
|
3943
|
+
} catch (error) {
|
|
3944
|
+
if (String(error).includes('UNIQUE constraint failed')) {
|
|
3945
|
+
throw new Error(`QUERY_PLAN_SOURCE_CONFLICT:${record.sourceIntentHash}`);
|
|
3946
|
+
}
|
|
3947
|
+
throw error;
|
|
3948
|
+
}
|
|
3949
|
+
return this.getChannelQueryPlan(record.planHash)!;
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
getChannelQueryPlan(planHash: string): ChannelQueryPlanRecord | null {
|
|
3953
|
+
const row = this.db.prepare(`
|
|
3954
|
+
SELECT * FROM devflow_channel_query_plans WHERE plan_hash = ?
|
|
3955
|
+
`).get(planHash) as Record<string, unknown> | undefined;
|
|
3956
|
+
return row ? mapChannelQueryPlanRow(row) : null;
|
|
3957
|
+
}
|
|
3958
|
+
|
|
3959
|
+
getChannelQueryPlanForIntent(input: {
|
|
3960
|
+
projectRoot: string;
|
|
3961
|
+
sessionId: string;
|
|
3962
|
+
turnId: string;
|
|
3963
|
+
sourceIntentHash: string;
|
|
3964
|
+
}): ChannelQueryPlanRecord | null {
|
|
3965
|
+
const row = this.db.prepare(`
|
|
3966
|
+
SELECT * FROM devflow_channel_query_plans
|
|
3967
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND source_intent_hash = ?
|
|
3968
|
+
ORDER BY created_at DESC LIMIT 1
|
|
3969
|
+
`).get(
|
|
3970
|
+
input.projectRoot,
|
|
3971
|
+
input.sessionId,
|
|
3972
|
+
input.turnId,
|
|
3973
|
+
input.sourceIntentHash,
|
|
3974
|
+
) as Record<string, unknown> | undefined;
|
|
3975
|
+
return row ? mapChannelQueryPlanRow(row) : null;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
appendToolNameResolution(record: ToolNameResolutionRecord): boolean {
|
|
3979
|
+
assertTaskIdentity(record);
|
|
3980
|
+
if (!Number.isSafeInteger(record.attempt) || record.attempt < 0) {
|
|
3981
|
+
throw new Error('TOOL_RESOLUTION_INVALID_ATTEMPT');
|
|
3982
|
+
}
|
|
3983
|
+
return this.db.prepare(`
|
|
3984
|
+
INSERT OR IGNORE INTO devflow_tool_name_resolutions (
|
|
3985
|
+
id, source_hash, project_root, project_id, host_id, session_id, turn_id,
|
|
3986
|
+
request_id, execution_id, requested_name, canonical_name, projected_name,
|
|
3987
|
+
status, attempt, capability_mechanism, reason, created_at
|
|
3988
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3989
|
+
`).run(
|
|
3990
|
+
record.id, record.sourceHash, record.projectRoot, record.projectId, record.hostId,
|
|
3991
|
+
record.sessionId, record.turnId, record.requestId, record.executionId ?? null,
|
|
3992
|
+
record.requestedName, record.canonicalName ?? null, record.projectedName ?? null,
|
|
3993
|
+
record.status, record.attempt, record.capabilityMechanism ?? null,
|
|
3994
|
+
record.reason ?? null, record.createdAt,
|
|
3995
|
+
).changes > 0;
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
listToolNameResolutions(input: {
|
|
3999
|
+
projectRoot: string;
|
|
4000
|
+
sessionId: string;
|
|
4001
|
+
requestId?: string;
|
|
4002
|
+
}): ToolNameResolutionRecord[] {
|
|
4003
|
+
const predicates = ['project_root = ?', 'session_id = ?'];
|
|
4004
|
+
const params: unknown[] = [input.projectRoot, input.sessionId];
|
|
4005
|
+
if (input.requestId) { predicates.push('request_id = ?'); params.push(input.requestId); }
|
|
4006
|
+
return (this.db.prepare(`
|
|
4007
|
+
SELECT * FROM devflow_tool_name_resolutions WHERE ${predicates.join(' AND ')}
|
|
4008
|
+
ORDER BY created_at ASC, attempt ASC, id ASC
|
|
4009
|
+
`).all(...params) as Array<Record<string, unknown>>).map(mapToolNameResolutionRow);
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
appendTerminalTransition(record: TerminalTransitionRecord): TerminalTransitionRecord {
|
|
4013
|
+
assertTaskIdentity(record);
|
|
4014
|
+
if (!Number.isSafeInteger(record.sequence) || record.sequence <= 0) {
|
|
4015
|
+
throw new Error('TERMINAL_TRANSITION_INVALID_SEQUENCE');
|
|
4016
|
+
}
|
|
4017
|
+
const existing = this.getTerminalTransition(record.receiptId);
|
|
4018
|
+
if (existing) {
|
|
4019
|
+
if (stableSemanticJson(existing) !== stableSemanticJson(record)) {
|
|
4020
|
+
throw new Error(`TERMINAL_RECEIPT_CONFLICT:${record.receiptId}`);
|
|
4021
|
+
}
|
|
4022
|
+
return existing;
|
|
4023
|
+
}
|
|
4024
|
+
const latest = this.getLatestTerminalTransition(record.projectRoot, record.sessionId, record.turnId);
|
|
4025
|
+
if (record.sequence !== (latest?.sequence ?? 0) + 1) {
|
|
4026
|
+
throw new Error(`TERMINAL_TRANSITION_SEQUENCE:${latest?.sequence ?? 0}->${record.sequence}`);
|
|
4027
|
+
}
|
|
4028
|
+
if ((latest?.toState ?? undefined) !== record.fromState) {
|
|
4029
|
+
throw new Error(`TERMINAL_TRANSITION_FROM_MISMATCH:${record.fromState ?? 'none'}`);
|
|
4030
|
+
}
|
|
4031
|
+
this.db.prepare(`
|
|
4032
|
+
INSERT INTO devflow_terminal_transitions (
|
|
4033
|
+
receipt_id, project_root, project_id, host_id, session_id, turn_id, request_id,
|
|
4034
|
+
execution_id, sequence, from_state, to_state, source_receipt_id, reason,
|
|
4035
|
+
payload_json, created_at
|
|
4036
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4037
|
+
`).run(
|
|
4038
|
+
record.receiptId, record.projectRoot, record.projectId, record.hostId,
|
|
4039
|
+
record.sessionId, record.turnId, record.requestId, record.executionId ?? null,
|
|
4040
|
+
record.sequence, record.fromState ?? null, record.toState,
|
|
4041
|
+
record.sourceReceiptId ?? null, record.reason, stableSemanticJson(record.payload),
|
|
4042
|
+
record.createdAt,
|
|
4043
|
+
);
|
|
4044
|
+
return this.getTerminalTransition(record.receiptId)!;
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
getTerminalTransition(receiptId: string): TerminalTransitionRecord | null {
|
|
4048
|
+
const row = this.db.prepare(`
|
|
4049
|
+
SELECT * FROM devflow_terminal_transitions WHERE receipt_id = ?
|
|
4050
|
+
`).get(receiptId) as Record<string, unknown> | undefined;
|
|
4051
|
+
return row ? mapTerminalTransitionRow(row) : null;
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
getLatestTerminalTransition(
|
|
4055
|
+
projectRoot: string,
|
|
4056
|
+
sessionId: string,
|
|
4057
|
+
turnId: string,
|
|
4058
|
+
): TerminalTransitionRecord | null {
|
|
4059
|
+
const row = this.db.prepare(`
|
|
4060
|
+
SELECT * FROM devflow_terminal_transitions
|
|
4061
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
4062
|
+
ORDER BY sequence DESC LIMIT 1
|
|
4063
|
+
`).get(projectRoot, sessionId, turnId) as Record<string, unknown> | undefined;
|
|
4064
|
+
return row ? mapTerminalTransitionRow(row) : null;
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
appendTranscriptCheckpoint(record: TranscriptCheckpointRecord): boolean {
|
|
4068
|
+
if (!record.id || !record.projectRoot || !record.hostId || !record.sessionId
|
|
4069
|
+
|| !record.sourcePath || !record.sourceHash) {
|
|
4070
|
+
throw new Error('TRANSCRIPT_CHECKPOINT_INVALID_IDENTITY');
|
|
4071
|
+
}
|
|
4072
|
+
if (!Number.isSafeInteger(record.byteOffset) || record.byteOffset < 0
|
|
4073
|
+
|| !Number.isSafeInteger(record.eventCount) || record.eventCount < 0) {
|
|
4074
|
+
throw new Error('TRANSCRIPT_CHECKPOINT_INVALID_POSITION');
|
|
4075
|
+
}
|
|
4076
|
+
return this.db.prepare(`
|
|
4077
|
+
INSERT OR IGNORE INTO devflow_transcript_checkpoints (
|
|
4078
|
+
id, project_root, host_id, session_id, source_path, source_hash,
|
|
4079
|
+
byte_offset, event_count, created_at
|
|
4080
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4081
|
+
`).run(
|
|
4082
|
+
record.id, record.projectRoot, record.hostId, record.sessionId,
|
|
4083
|
+
record.sourcePath, record.sourceHash, record.byteOffset, record.eventCount,
|
|
4084
|
+
record.createdAt,
|
|
4085
|
+
).changes > 0;
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
getLatestTranscriptCheckpoint(input: {
|
|
4089
|
+
projectRoot: string;
|
|
4090
|
+
hostId: string;
|
|
4091
|
+
sessionId: string;
|
|
4092
|
+
sourcePath: string;
|
|
4093
|
+
}): TranscriptCheckpointRecord | null {
|
|
4094
|
+
const row = this.db.prepare(`
|
|
4095
|
+
SELECT * FROM devflow_transcript_checkpoints
|
|
4096
|
+
WHERE project_root = ? AND host_id = ? AND session_id = ? AND source_path = ?
|
|
4097
|
+
ORDER BY created_at DESC, byte_offset DESC LIMIT 1
|
|
4098
|
+
`).get(input.projectRoot, input.hostId, input.sessionId, input.sourcePath) as Record<string, unknown> | undefined;
|
|
4099
|
+
return row ? mapTranscriptCheckpointRow(row) : null;
|
|
4100
|
+
}
|
|
4101
|
+
|
|
3688
4102
|
listRetrievalLedgerEvents(options: {
|
|
3689
4103
|
projectRoot: string; sessionId?: string; executionId?: string; requestId?: string;
|
|
3690
4104
|
}): RetrievalLedgerEventRecord[] {
|
|
@@ -4924,3 +5338,22 @@ function canonicalWorkflowProjectRoot(projectRoot: string): string {
|
|
|
4924
5338
|
const resolved = resolve(projectRoot);
|
|
4925
5339
|
try { return realpathSync(resolved); } catch { return resolved; }
|
|
4926
5340
|
}
|
|
5341
|
+
|
|
5342
|
+
function sameTaskIdentity(
|
|
5343
|
+
left: {
|
|
5344
|
+
projectRoot: string; projectId: string; hostId: string; sessionId: string;
|
|
5345
|
+
turnId: string; requestId: string; executionId?: string;
|
|
5346
|
+
},
|
|
5347
|
+
right: {
|
|
5348
|
+
projectRoot: string; projectId: string; hostId: string; sessionId: string;
|
|
5349
|
+
turnId: string; requestId: string; executionId?: string;
|
|
5350
|
+
},
|
|
5351
|
+
): boolean {
|
|
5352
|
+
return left.projectRoot === right.projectRoot
|
|
5353
|
+
&& left.projectId === right.projectId
|
|
5354
|
+
&& left.hostId === right.hostId
|
|
5355
|
+
&& left.sessionId === right.sessionId
|
|
5356
|
+
&& left.turnId === right.turnId
|
|
5357
|
+
&& left.requestId === right.requestId
|
|
5358
|
+
&& left.executionId === right.executionId;
|
|
5359
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -65,6 +65,24 @@ export {
|
|
|
65
65
|
mapSessionObligationRow,
|
|
66
66
|
normalizeTurnId,
|
|
67
67
|
} from './obligation-ledger';
|
|
68
|
+
export {
|
|
69
|
+
assertTaskIdentity,
|
|
70
|
+
mapChannelQueryPlanRow,
|
|
71
|
+
mapTaskIntentArtifactRow,
|
|
72
|
+
mapTerminalTransitionRow,
|
|
73
|
+
mapToolNameResolutionRow,
|
|
74
|
+
mapTranscriptCheckpointRow,
|
|
75
|
+
stableSemanticJson,
|
|
76
|
+
} from './task-semantic-control';
|
|
77
|
+
export type {
|
|
78
|
+
ChannelQueryPlanRecord,
|
|
79
|
+
TaskIdentityRecord,
|
|
80
|
+
TaskIntentArtifactRecord,
|
|
81
|
+
TerminalTransitionRecord,
|
|
82
|
+
ToolNameResolutionRecord,
|
|
83
|
+
ToolNameResolutionStatus,
|
|
84
|
+
TranscriptCheckpointRecord,
|
|
85
|
+
} from './task-semantic-control';
|
|
68
86
|
export {
|
|
69
87
|
LEGAL_WORKFLOW_WORKER_TRANSITIONS,
|
|
70
88
|
TERMINAL_WORKFLOW_WORKER_STATES,
|