@devflow-tools/database 0.17.8 → 0.18.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/LICENSE +21 -0
- package/dist/data/LocalDataProvider.d.ts +63 -0
- package/dist/data/LocalDataProvider.js +332 -0
- package/dist/data/default-enforcer-rules.d.ts +3 -0
- package/dist/data/default-enforcer-rules.js +170 -0
- package/dist/data/devflow-schema.d.ts +2 -0
- package/dist/data/devflow-schema.js +80 -0
- package/dist/database.d.ts +45 -7
- package/dist/database.js +583 -125
- package/dist/index.d.ts +8 -4
- package/dist/index.js +11 -5
- package/dist/retrieval-ledger.d.ts +16 -1
- package/dist/retrieval-ledger.js +6 -0
- package/dist/retrieval-runtime.d.ts +39 -0
- package/dist/retrieval-runtime.js +2 -0
- package/dist/task-aggregate.d.ts +49 -0
- package/dist/task-aggregate.js +2 -0
- package/dist/task-semantic-control.d.ts +8 -1
- package/dist/task-semantic-control.js +2 -4
- package/package.json +15 -3
- package/CHANGELOG.md +0 -844
- package/__tests__/database.failure-category.test.ts +0 -44
- package/__tests__/database.host-actions.test.ts +0 -405
- package/__tests__/database.learning-candidates.test.ts +0 -93
- package/__tests__/database.memory-turn-receipts.test.ts +0 -98
- package/__tests__/database.retrieval-ledger.test.ts +0 -68
- package/__tests__/database.retrieval-sessions.test.ts +0 -79
- package/__tests__/database.semantic-resolution.test.ts +0 -87
- package/__tests__/database.skill-executions.test.ts +0 -456
- package/__tests__/database.task-runtime.test.ts +0 -73
- package/__tests__/database.test.ts +0 -177
- package/__tests__/database.work-queue.test.ts +0 -274
- package/__tests__/database.workflow-workers.test.ts +0 -60
- package/__tests__/node-sqlite.test.ts +0 -68
- package/src/database.ts +0 -5853
- package/src/host-actions.ts +0 -211
- package/src/index.ts +0 -153
- package/src/learning-candidates.ts +0 -181
- package/src/node-sqlite.ts +0 -60
- package/src/obligation-ledger.ts +0 -57
- package/src/retrieval-ledger.ts +0 -35
- package/src/retrieval-sessions.ts +0 -196
- package/src/semantic-resolution.ts +0 -181
- package/src/task-runtime.ts +0 -169
- package/src/task-semantic-control.ts +0 -270
- package/src/types.ts +0 -17
- package/src/work-queue.ts +0 -123
- package/src/workflow-workers.ts +0 -198
- package/tsconfig.json +0 -19
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -41
package/dist/database.js
CHANGED
|
@@ -14,9 +14,10 @@ const host_actions_1 = require("./host-actions");
|
|
|
14
14
|
const retrieval_sessions_1 = require("./retrieval-sessions");
|
|
15
15
|
const learning_candidates_1 = require("./learning-candidates");
|
|
16
16
|
const workflow_workers_1 = require("./workflow-workers");
|
|
17
|
+
const retrieval_ledger_1 = require("./retrieval-ledger");
|
|
17
18
|
const task_semantic_control_1 = require("./task-semantic-control");
|
|
18
19
|
const task_runtime_1 = require("./task-runtime");
|
|
19
|
-
const
|
|
20
|
+
const sdk_1 = require("@devflow-tools/sdk");
|
|
20
21
|
const CONTEXT_REQUIRED_SKILLS = new Set([
|
|
21
22
|
'react', 'vue', 'nest', 'nextjs', 'graphql', 'typescript',
|
|
22
23
|
]);
|
|
@@ -58,8 +59,7 @@ function obligationsForSkill(skillName, configured) {
|
|
|
58
59
|
return configuredTools.slice(0, 1);
|
|
59
60
|
}
|
|
60
61
|
function getGlobalDevFlowDbPath(home = (0, os_1.homedir)()) {
|
|
61
|
-
|
|
62
|
-
return (0, path_1.join)(stateDir, 'devflow.db');
|
|
62
|
+
return (0, path_1.join)((0, sdk_1.getDevFlowStateRoot)({ homeDir: home }), 'global', 'devflow.db');
|
|
63
63
|
}
|
|
64
64
|
function openGlobalDevFlowDatabase(home = (0, os_1.homedir)(), options) {
|
|
65
65
|
return new DevFlowDatabase(home, {
|
|
@@ -400,35 +400,6 @@ class DevFlowDatabase {
|
|
|
400
400
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_intent_source
|
|
401
401
|
ON devflow_task_intent_artifacts(project_root, session_id, source_hash, version);
|
|
402
402
|
|
|
403
|
-
CREATE TABLE IF NOT EXISTS devflow_semantic_resolutions (
|
|
404
|
-
frame_hash TEXT PRIMARY KEY,
|
|
405
|
-
project_root TEXT NOT NULL,
|
|
406
|
-
project_id TEXT NOT NULL,
|
|
407
|
-
host_id TEXT NOT NULL,
|
|
408
|
-
session_id TEXT NOT NULL,
|
|
409
|
-
turn_id TEXT NOT NULL,
|
|
410
|
-
request_id TEXT NOT NULL,
|
|
411
|
-
source_hash TEXT NOT NULL,
|
|
412
|
-
artifact_hash TEXT NOT NULL,
|
|
413
|
-
artifact_revision INTEGER NOT NULL CHECK(artifact_revision > 0),
|
|
414
|
-
supersedes_frame_hash TEXT,
|
|
415
|
-
state TEXT NOT NULL CHECK(state IN ('provisional', 'resolved', 'abstained', 'unavailable')),
|
|
416
|
-
generated_by TEXT NOT NULL CHECK(generated_by IN ('deterministic', 'embedding_router', 'host_semantic', 'hybrid')),
|
|
417
|
-
model_id TEXT,
|
|
418
|
-
model_revision TEXT,
|
|
419
|
-
route_catalog_version TEXT NOT NULL,
|
|
420
|
-
threshold_version TEXT NOT NULL,
|
|
421
|
-
route_json TEXT NOT NULL,
|
|
422
|
-
sampling_json TEXT NOT NULL,
|
|
423
|
-
conflicts_json TEXT NOT NULL,
|
|
424
|
-
frame_json TEXT NOT NULL,
|
|
425
|
-
duration_ms INTEGER NOT NULL CHECK(duration_ms >= 0),
|
|
426
|
-
created_at INTEGER NOT NULL,
|
|
427
|
-
UNIQUE(project_root, session_id, turn_id, artifact_revision)
|
|
428
|
-
);
|
|
429
|
-
CREATE INDEX IF NOT EXISTS idx_semantic_resolution_latest
|
|
430
|
-
ON devflow_semantic_resolutions(project_root, session_id, turn_id, artifact_revision DESC);
|
|
431
|
-
|
|
432
403
|
CREATE TABLE IF NOT EXISTS devflow_channel_query_plans (
|
|
433
404
|
plan_hash TEXT PRIMARY KEY,
|
|
434
405
|
source_intent_hash TEXT NOT NULL,
|
|
@@ -528,6 +499,109 @@ class DevFlowDatabase {
|
|
|
528
499
|
PRIMARY KEY(project_root, session_id, turn_id)
|
|
529
500
|
);
|
|
530
501
|
|
|
502
|
+
CREATE TABLE IF NOT EXISTS devflow_task_definitions_v2 (
|
|
503
|
+
definition_hash TEXT PRIMARY KEY,
|
|
504
|
+
project_root TEXT NOT NULL,
|
|
505
|
+
session_id TEXT NOT NULL,
|
|
506
|
+
turn_id TEXT NOT NULL,
|
|
507
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
508
|
+
definition_json TEXT NOT NULL,
|
|
509
|
+
created_at INTEGER NOT NULL,
|
|
510
|
+
UNIQUE(project_root, session_id, turn_id, revision)
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
CREATE TABLE IF NOT EXISTS devflow_runtime_activations_v1 (
|
|
514
|
+
snapshot_hash TEXT NOT NULL,
|
|
515
|
+
project_root TEXT NOT NULL,
|
|
516
|
+
project_id TEXT NOT NULL,
|
|
517
|
+
host_id TEXT NOT NULL,
|
|
518
|
+
session_id TEXT NOT NULL,
|
|
519
|
+
turn_id TEXT NOT NULL,
|
|
520
|
+
request_id TEXT NOT NULL,
|
|
521
|
+
execution_id TEXT,
|
|
522
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
523
|
+
snapshot_json TEXT NOT NULL,
|
|
524
|
+
created_at INTEGER NOT NULL,
|
|
525
|
+
PRIMARY KEY(project_root, session_id, turn_id, revision)
|
|
526
|
+
);
|
|
527
|
+
CREATE INDEX IF NOT EXISTS idx_runtime_activation_hash_v1
|
|
528
|
+
ON devflow_runtime_activations_v1(snapshot_hash);
|
|
529
|
+
|
|
530
|
+
CREATE TABLE IF NOT EXISTS devflow_semantic_execution_contexts (
|
|
531
|
+
context_hash TEXT PRIMARY KEY,
|
|
532
|
+
project_root TEXT NOT NULL,
|
|
533
|
+
session_id TEXT NOT NULL,
|
|
534
|
+
turn_id TEXT NOT NULL,
|
|
535
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
536
|
+
facts_hash TEXT NOT NULL,
|
|
537
|
+
plan_hash TEXT NOT NULL,
|
|
538
|
+
activation_snapshot_hash TEXT NOT NULL,
|
|
539
|
+
definition_hash TEXT NOT NULL,
|
|
540
|
+
context_json TEXT NOT NULL,
|
|
541
|
+
created_at INTEGER NOT NULL,
|
|
542
|
+
UNIQUE(project_root, session_id, turn_id, revision)
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
CREATE TABLE IF NOT EXISTS devflow_task_aggregate_events_v2 (
|
|
546
|
+
event_id TEXT PRIMARY KEY,
|
|
547
|
+
idempotency_key TEXT NOT NULL UNIQUE,
|
|
548
|
+
project_root TEXT NOT NULL,
|
|
549
|
+
project_id TEXT NOT NULL,
|
|
550
|
+
host_id TEXT NOT NULL,
|
|
551
|
+
session_id TEXT NOT NULL,
|
|
552
|
+
turn_id TEXT NOT NULL,
|
|
553
|
+
request_id TEXT NOT NULL,
|
|
554
|
+
execution_id TEXT,
|
|
555
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
556
|
+
sequence INTEGER NOT NULL CHECK(sequence > 0),
|
|
557
|
+
kind TEXT NOT NULL,
|
|
558
|
+
producer TEXT NOT NULL,
|
|
559
|
+
producer_version TEXT NOT NULL,
|
|
560
|
+
causation_id TEXT,
|
|
561
|
+
correlation_id TEXT,
|
|
562
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
563
|
+
event_json TEXT NOT NULL,
|
|
564
|
+
created_at INTEGER NOT NULL,
|
|
565
|
+
UNIQUE(project_root, session_id, turn_id, revision, sequence)
|
|
566
|
+
);
|
|
567
|
+
CREATE INDEX IF NOT EXISTS idx_task_aggregate_event_identity_v2
|
|
568
|
+
ON devflow_task_aggregate_events_v2(project_root, session_id, turn_id, revision, sequence);
|
|
569
|
+
|
|
570
|
+
CREATE TABLE IF NOT EXISTS devflow_task_aggregates_v2 (
|
|
571
|
+
project_root TEXT NOT NULL,
|
|
572
|
+
session_id TEXT NOT NULL,
|
|
573
|
+
turn_id TEXT NOT NULL,
|
|
574
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
575
|
+
definition_hash TEXT NOT NULL,
|
|
576
|
+
activation_snapshot_hash TEXT NOT NULL,
|
|
577
|
+
last_event_sequence INTEGER NOT NULL CHECK(last_event_sequence > 0),
|
|
578
|
+
snapshot_json TEXT NOT NULL,
|
|
579
|
+
snapshot_hash TEXT NOT NULL,
|
|
580
|
+
updated_at INTEGER NOT NULL,
|
|
581
|
+
PRIMARY KEY(project_root, session_id, turn_id, revision)
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
CREATE TABLE IF NOT EXISTS devflow_task_outbox_v2 (
|
|
585
|
+
operation_id TEXT PRIMARY KEY,
|
|
586
|
+
event_id TEXT NOT NULL,
|
|
587
|
+
project_root TEXT NOT NULL,
|
|
588
|
+
session_id TEXT NOT NULL,
|
|
589
|
+
turn_id TEXT NOT NULL,
|
|
590
|
+
revision INTEGER NOT NULL CHECK(revision > 0),
|
|
591
|
+
kind TEXT NOT NULL,
|
|
592
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
593
|
+
state TEXT NOT NULL CHECK(state IN ('pending','leased','completed','dead_letter')),
|
|
594
|
+
lease_owner TEXT,
|
|
595
|
+
lease_expires_at INTEGER,
|
|
596
|
+
fencing_token INTEGER NOT NULL DEFAULT 0 CHECK(fencing_token >= 0),
|
|
597
|
+
attempts INTEGER NOT NULL DEFAULT 0 CHECK(attempts >= 0),
|
|
598
|
+
created_at INTEGER NOT NULL,
|
|
599
|
+
updated_at INTEGER NOT NULL,
|
|
600
|
+
FOREIGN KEY(event_id) REFERENCES devflow_task_aggregate_events_v2(event_id)
|
|
601
|
+
);
|
|
602
|
+
CREATE INDEX IF NOT EXISTS idx_task_outbox_ready_v2
|
|
603
|
+
ON devflow_task_outbox_v2(project_root, state, updated_at);
|
|
604
|
+
|
|
531
605
|
CREATE TABLE IF NOT EXISTS devflow_transcript_checkpoints (
|
|
532
606
|
id TEXT PRIMARY KEY,
|
|
533
607
|
project_root TEXT NOT NULL,
|
|
@@ -597,6 +671,53 @@ class DevFlowDatabase {
|
|
|
597
671
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_retrieval_cycles_receipt
|
|
598
672
|
ON devflow_retrieval_cycles(receipt);
|
|
599
673
|
|
|
674
|
+
CREATE TABLE IF NOT EXISTS devflow_retrieval_sessions_v2 (
|
|
675
|
+
id TEXT PRIMARY KEY,
|
|
676
|
+
project_root TEXT NOT NULL,
|
|
677
|
+
project_id TEXT NOT NULL,
|
|
678
|
+
host_id TEXT NOT NULL,
|
|
679
|
+
task_session_id TEXT NOT NULL,
|
|
680
|
+
turn_id TEXT NOT NULL,
|
|
681
|
+
request_id TEXT NOT NULL,
|
|
682
|
+
execution_id TEXT,
|
|
683
|
+
task_revision INTEGER NOT NULL CHECK(task_revision > 0),
|
|
684
|
+
facts_hash TEXT NOT NULL,
|
|
685
|
+
plan_hash TEXT NOT NULL,
|
|
686
|
+
activation_snapshot_hash TEXT NOT NULL,
|
|
687
|
+
coordinator_version TEXT NOT NULL,
|
|
688
|
+
state TEXT NOT NULL CHECK(state IN ('open','sufficient','gap_actionable','gap_unavailable','abstain')),
|
|
689
|
+
cycle INTEGER NOT NULL DEFAULT 0 CHECK(cycle BETWEEN 0 AND 3),
|
|
690
|
+
no_gain_count INTEGER NOT NULL DEFAULT 0 CHECK(no_gain_count BETWEEN 0 AND 2),
|
|
691
|
+
max_cycles INTEGER NOT NULL DEFAULT 3 CHECK(max_cycles = 3),
|
|
692
|
+
initial_token_budget INTEGER NOT NULL CHECK(initial_token_budget >= 0),
|
|
693
|
+
remaining_token_budget INTEGER NOT NULL CHECK(remaining_token_budget >= 0),
|
|
694
|
+
final_receipt_hash TEXT,
|
|
695
|
+
created_at INTEGER NOT NULL,
|
|
696
|
+
updated_at INTEGER NOT NULL,
|
|
697
|
+
expires_at INTEGER NOT NULL,
|
|
698
|
+
UNIQUE(project_root, task_session_id, turn_id, task_revision, coordinator_version)
|
|
699
|
+
);
|
|
700
|
+
CREATE INDEX IF NOT EXISTS idx_retrieval_sessions_v2_owner
|
|
701
|
+
ON devflow_retrieval_sessions_v2(project_root, task_session_id, state, updated_at);
|
|
702
|
+
|
|
703
|
+
CREATE TABLE IF NOT EXISTS devflow_retrieval_cycles_v2 (
|
|
704
|
+
retrieval_session_id TEXT NOT NULL,
|
|
705
|
+
cycle INTEGER NOT NULL CHECK(cycle BETWEEN 1 AND 3),
|
|
706
|
+
signature_hashes_json TEXT NOT NULL,
|
|
707
|
+
receipt_json TEXT NOT NULL,
|
|
708
|
+
receipt_hash TEXT NOT NULL UNIQUE,
|
|
709
|
+
evidence_hash TEXT NOT NULL,
|
|
710
|
+
token_cost INTEGER NOT NULL CHECK(token_cost >= 0),
|
|
711
|
+
stable_ref_gain INTEGER NOT NULL CHECK(stable_ref_gain >= 0),
|
|
712
|
+
no_gain_count INTEGER NOT NULL CHECK(no_gain_count BETWEEN 0 AND 2),
|
|
713
|
+
sufficiency TEXT NOT NULL CHECK(sufficiency IN ('sufficient','gap_actionable','gap_unavailable','abstain')),
|
|
714
|
+
created_at INTEGER NOT NULL,
|
|
715
|
+
PRIMARY KEY(retrieval_session_id, cycle),
|
|
716
|
+
FOREIGN KEY(retrieval_session_id) REFERENCES devflow_retrieval_sessions_v2(id)
|
|
717
|
+
);
|
|
718
|
+
CREATE INDEX IF NOT EXISTS idx_retrieval_cycle_v2_evidence
|
|
719
|
+
ON devflow_retrieval_cycles_v2(retrieval_session_id, evidence_hash);
|
|
720
|
+
|
|
600
721
|
CREATE TABLE IF NOT EXISTS devflow_memory_distill_checkpoints (
|
|
601
722
|
id TEXT PRIMARY KEY,
|
|
602
723
|
project_root TEXT NOT NULL,
|
|
@@ -3035,6 +3156,119 @@ class DevFlowDatabase {
|
|
|
3035
3156
|
throw error;
|
|
3036
3157
|
}
|
|
3037
3158
|
}
|
|
3159
|
+
createRetrievalSessionV2(input) {
|
|
3160
|
+
if (!input.id || !Number.isSafeInteger(input.taskRevision) || input.taskRevision < 1
|
|
3161
|
+
|| !Number.isSafeInteger(input.tokenBudget) || input.tokenBudget < 0) {
|
|
3162
|
+
throw new Error('RETRIEVAL_SESSION_V2_INVALID');
|
|
3163
|
+
}
|
|
3164
|
+
const existing = this.getRetrievalSessionV2(input.id);
|
|
3165
|
+
if (existing) {
|
|
3166
|
+
const compatible = existing.identity.projectRoot === input.identity.projectRoot
|
|
3167
|
+
&& existing.identity.sessionId === input.identity.sessionId
|
|
3168
|
+
&& existing.identity.turnId === input.identity.turnId
|
|
3169
|
+
&& existing.taskRevision === input.taskRevision
|
|
3170
|
+
&& existing.factsHash === input.factsHash
|
|
3171
|
+
&& existing.planHash === input.planHash
|
|
3172
|
+
&& existing.activationSnapshotHash === input.activationSnapshotHash
|
|
3173
|
+
&& existing.coordinatorVersion === input.coordinatorVersion
|
|
3174
|
+
&& existing.initialTokenBudget === input.tokenBudget;
|
|
3175
|
+
if (!compatible)
|
|
3176
|
+
throw new Error('RETRIEVAL_SESSION_V2_CONFLICT');
|
|
3177
|
+
return existing;
|
|
3178
|
+
}
|
|
3179
|
+
const now = input.createdAt ?? Date.now();
|
|
3180
|
+
const identity = input.identity;
|
|
3181
|
+
this.db.prepare(`INSERT INTO devflow_retrieval_sessions_v2 (
|
|
3182
|
+
id, project_root, project_id, host_id, task_session_id, turn_id, request_id,
|
|
3183
|
+
execution_id, task_revision, facts_hash, plan_hash, activation_snapshot_hash,
|
|
3184
|
+
coordinator_version, state, cycle, no_gain_count, max_cycles,
|
|
3185
|
+
initial_token_budget, remaining_token_budget, created_at, updated_at, expires_at
|
|
3186
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open', 0, 0, 3, ?, ?, ?, ?, ?)`)
|
|
3187
|
+
.run(input.id, identity.projectRoot, identity.projectId, identity.hostId, identity.sessionId, identity.turnId, identity.requestId, identity.executionId ?? null, input.taskRevision, input.factsHash, input.planHash, input.activationSnapshotHash, input.coordinatorVersion, input.tokenBudget, input.tokenBudget, now, now, input.expiresAt);
|
|
3188
|
+
return this.getRetrievalSessionV2(input.id);
|
|
3189
|
+
}
|
|
3190
|
+
getRetrievalSessionV2(id) {
|
|
3191
|
+
const row = this.db.prepare('SELECT * FROM devflow_retrieval_sessions_v2 WHERE id = ?')
|
|
3192
|
+
.get(id);
|
|
3193
|
+
return row ? mapRetrievalSessionV2(row) : null;
|
|
3194
|
+
}
|
|
3195
|
+
listRetrievalCyclesV2(sessionId) {
|
|
3196
|
+
return this.db.prepare(`SELECT receipt_json FROM devflow_retrieval_cycles_v2
|
|
3197
|
+
WHERE retrieval_session_id = ? ORDER BY cycle`).all(sessionId)
|
|
3198
|
+
.map(row => JSON.parse(row.receipt_json));
|
|
3199
|
+
}
|
|
3200
|
+
appendRetrievalCycleV2(input) {
|
|
3201
|
+
return this.db.transaction(() => {
|
|
3202
|
+
const session = this.getRetrievalSessionV2(input.sessionId);
|
|
3203
|
+
if (!session)
|
|
3204
|
+
throw new Error(`RETRIEVAL_SESSION_V2_NOT_FOUND:${input.sessionId}`);
|
|
3205
|
+
if (session.identity.projectRoot !== input.projectRoot
|
|
3206
|
+
|| session.identity.sessionId !== input.taskSessionId)
|
|
3207
|
+
throw new Error('RETRIEVAL_SESSION_V2_IDENTITY_MISMATCH');
|
|
3208
|
+
if (input.receipt.sessionId !== session.id)
|
|
3209
|
+
throw new Error('RETRIEVAL_CYCLE_V2_SESSION_MISMATCH');
|
|
3210
|
+
const { receiptHash: cycleHash, ...cycleBody } = input.receipt;
|
|
3211
|
+
const { receiptHash: compositeHash, ...compositeBody } = input.receipt.compositeReceipt;
|
|
3212
|
+
if ((0, task_runtime_1.stableTaskRuntimeHash)(cycleBody) !== cycleHash
|
|
3213
|
+
|| (0, task_runtime_1.stableTaskRuntimeHash)(compositeBody) !== compositeHash) {
|
|
3214
|
+
throw new Error('RETRIEVAL_CYCLE_V2_HASH_MISMATCH');
|
|
3215
|
+
}
|
|
3216
|
+
if (input.receipt.compositeReceipt.factsHash !== session.factsHash
|
|
3217
|
+
|| input.receipt.compositeReceipt.planHash !== session.planHash
|
|
3218
|
+
|| input.receipt.compositeReceipt.activationSnapshotHash !== session.activationSnapshotHash
|
|
3219
|
+
|| input.receipt.compositeReceipt.coordinatorVersion !== session.coordinatorVersion) {
|
|
3220
|
+
throw new Error('RETRIEVAL_CYCLE_V2_BINDING_MISMATCH');
|
|
3221
|
+
}
|
|
3222
|
+
const signaturesValid = input.receipt.requestSignatures.every(signature => sameTaskIdentity(signature.identity, session.identity)
|
|
3223
|
+
&& signature.taskRevision === session.taskRevision
|
|
3224
|
+
&& signature.factsHash === session.factsHash
|
|
3225
|
+
&& signature.planHash === session.planHash
|
|
3226
|
+
&& signature.activationSnapshotHash === session.activationSnapshotHash);
|
|
3227
|
+
if (!signaturesValid)
|
|
3228
|
+
throw new Error('RETRIEVAL_SIGNATURE_V2_BINDING_MISMATCH');
|
|
3229
|
+
const existing = this.db.prepare(`SELECT receipt_json FROM devflow_retrieval_cycles_v2
|
|
3230
|
+
WHERE retrieval_session_id = ? AND cycle = ?`).get(input.sessionId, input.receipt.cycle);
|
|
3231
|
+
if (existing?.receipt_json) {
|
|
3232
|
+
const receipt = JSON.parse(existing.receipt_json);
|
|
3233
|
+
if (receipt.receiptHash !== input.receipt.receiptHash)
|
|
3234
|
+
throw new Error('RETRIEVAL_CYCLE_V2_CONFLICT');
|
|
3235
|
+
return session;
|
|
3236
|
+
}
|
|
3237
|
+
if (session.state !== 'open' || session.expiresAt <= input.receipt.createdAt) {
|
|
3238
|
+
throw new Error(`RETRIEVAL_SESSION_V2_TERMINAL:${session.state}`);
|
|
3239
|
+
}
|
|
3240
|
+
if (input.receipt.cycle !== session.cycle + 1 || input.receipt.cycle > 3) {
|
|
3241
|
+
throw new Error('RETRIEVAL_CYCLE_V2_SEQUENCE');
|
|
3242
|
+
}
|
|
3243
|
+
const priorCycles = this.listRetrievalCyclesV2(input.sessionId);
|
|
3244
|
+
const priorRefs = new Set(priorCycles.flatMap(cycle => cycle.selectedRefs));
|
|
3245
|
+
const expectedGain = input.receipt.selectedRefs.filter(ref => !priorRefs.has(ref)).length;
|
|
3246
|
+
const expectedNoGain = expectedGain > 0 ? 0 : Math.min(2, session.noGainCount + 1);
|
|
3247
|
+
if (input.receipt.stableRefGain !== expectedGain || input.receipt.noGainCount !== expectedNoGain) {
|
|
3248
|
+
throw new Error('RETRIEVAL_CYCLE_V2_GAIN_MISMATCH');
|
|
3249
|
+
}
|
|
3250
|
+
if (input.remainingTokenBudget !== session.remainingTokenBudget - input.receipt.tokenCost
|
|
3251
|
+
|| input.remainingTokenBudget < 0)
|
|
3252
|
+
throw new Error('RETRIEVAL_CYCLE_V2_BUDGET');
|
|
3253
|
+
this.db.prepare(`INSERT INTO devflow_retrieval_cycles_v2 (
|
|
3254
|
+
retrieval_session_id, cycle, signature_hashes_json, receipt_json, receipt_hash,
|
|
3255
|
+
evidence_hash, token_cost, stable_ref_gain, no_gain_count, sufficiency, created_at
|
|
3256
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
3257
|
+
.run(input.sessionId, input.receipt.cycle, (0, task_runtime_1.stableTaskRuntimeJson)(input.receipt.requestSignatures.map(item => item.signatureHash)), (0, task_runtime_1.stableTaskRuntimeJson)(input.receipt), input.receipt.receiptHash, input.receipt.evidenceHash, input.receipt.tokenCost, input.receipt.stableRefGain, input.receipt.noGainCount, input.receipt.sufficiency, input.receipt.createdAt);
|
|
3258
|
+
const terminal = input.receipt.sufficiency === 'sufficient'
|
|
3259
|
+
|| input.receipt.sufficiency === 'gap_unavailable'
|
|
3260
|
+
|| input.receipt.sufficiency === 'abstain'
|
|
3261
|
+
|| input.receipt.noGainCount >= 2
|
|
3262
|
+
|| input.receipt.cycle >= 3
|
|
3263
|
+
|| input.remainingTokenBudget === 0;
|
|
3264
|
+
const state = terminal ? input.receipt.sufficiency : 'open';
|
|
3265
|
+
this.db.prepare(`UPDATE devflow_retrieval_sessions_v2 SET
|
|
3266
|
+
state = ?, cycle = ?, no_gain_count = ?, remaining_token_budget = ?,
|
|
3267
|
+
final_receipt_hash = ?, updated_at = ? WHERE id = ?`)
|
|
3268
|
+
.run(state, input.receipt.cycle, input.receipt.noGainCount, input.remainingTokenBudget, terminal ? input.receipt.compositeReceipt.receiptHash : null, input.receipt.createdAt, input.sessionId);
|
|
3269
|
+
return this.getRetrievalSessionV2(input.sessionId);
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3038
3272
|
getRetrievalSession(projectRoot, sessionId, id) {
|
|
3039
3273
|
const row = this.db.prepare(`
|
|
3040
3274
|
SELECT * FROM devflow_retrieval_sessions
|
|
@@ -3294,7 +3528,13 @@ class DevFlowDatabase {
|
|
|
3294
3528
|
`).run(event.id, event.projectRoot, event.sessionId, event.executionId, event.requestId ?? null, event.selectionType, event.candidateId, event.toolName, event.toolUseId ?? null, event.selectedAt).changes > 0;
|
|
3295
3529
|
}
|
|
3296
3530
|
appendRetrievalLedgerEvent(event) {
|
|
3297
|
-
if ((
|
|
3531
|
+
if (!new Set([
|
|
3532
|
+
'eligible', 'candidate', 'selected', 'consumed', 'verified',
|
|
3533
|
+
'positive_outcome', 'negative_outcome', 'unknown_outcome', 'rejected',
|
|
3534
|
+
]).has(event.stage)) {
|
|
3535
|
+
throw new Error(`RETRIEVAL_LEDGER_STAGE_INVALID:${event.stage}`);
|
|
3536
|
+
}
|
|
3537
|
+
if (event.stage === 'selected'
|
|
3298
3538
|
&& typeof event.payload.returnedFinalScore === 'number'
|
|
3299
3539
|
&& event.payload.returnedFinalScore > 0
|
|
3300
3540
|
&& event.finalScore === 0) {
|
|
@@ -3309,7 +3549,57 @@ class DevFlowDatabase {
|
|
|
3309
3549
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3310
3550
|
`).run(event.id, event.projectRoot, event.sessionId, event.executionId ?? null, event.turnId ?? null, event.requestId, event.contextReceipt, event.sourceType, event.sourceId, event.stage, event.rank ?? null, event.rawScore ?? null, event.normalizedScore ?? null, event.finalScore ?? null, JSON.stringify(event.applicability), event.reason ?? null, JSON.stringify(event.toolEvidence), event.verificationReceipt ?? null, JSON.stringify(event.payload), event.createdAt, event.schemaVersion ?? 'retrieval-ledger-event.v1', event.taskSpecHash ?? null, event.actor ?? null, event.sourceVersion ?? null, event.sourceContentHash ?? null, JSON.stringify(event.evidenceIds ?? []), event.reasonCode ?? null).changes > 0;
|
|
3311
3551
|
}
|
|
3552
|
+
verifyReceiptBoundRetrieval(input) {
|
|
3553
|
+
for (const [field, value] of Object.entries({
|
|
3554
|
+
projectRoot: input.projectRoot,
|
|
3555
|
+
sessionId: input.sessionId,
|
|
3556
|
+
executionId: input.executionId,
|
|
3557
|
+
contextReceipt: input.contextReceipt,
|
|
3558
|
+
toolUseId: input.toolUseId,
|
|
3559
|
+
verificationReceipt: input.verificationReceipt,
|
|
3560
|
+
})) {
|
|
3561
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
3562
|
+
throw new Error(`RETRIEVAL_LEDGER_INVALID_${field}`);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
if (!Number.isSafeInteger(input.verifiedAt) || input.verifiedAt < 0) {
|
|
3566
|
+
throw new Error('RETRIEVAL_LEDGER_INVALID_verifiedAt');
|
|
3567
|
+
}
|
|
3568
|
+
return this.db.transaction(() => {
|
|
3569
|
+
const selected = this.listRetrievalLedgerEvents({
|
|
3570
|
+
projectRoot: input.projectRoot,
|
|
3571
|
+
sessionId: input.sessionId,
|
|
3572
|
+
executionId: input.executionId,
|
|
3573
|
+
}).filter(event => event.stage === 'selected'
|
|
3574
|
+
&& event.contextReceipt === input.contextReceipt);
|
|
3575
|
+
if (selected.length === 0)
|
|
3576
|
+
throw new Error('RETRIEVAL_LEDGER_SELECTIONS_NOT_FOUND');
|
|
3577
|
+
for (const selection of selected) {
|
|
3578
|
+
const consumed = {
|
|
3579
|
+
...selection,
|
|
3580
|
+
id: (0, retrieval_ledger_1.retrievalLedgerTransitionId)(selection.id, 'consumed', input.toolUseId),
|
|
3581
|
+
stage: 'consumed',
|
|
3582
|
+
actor: 'devflow_report_step',
|
|
3583
|
+
reasonCode: 'verified_host_action',
|
|
3584
|
+
toolEvidence: [input.toolUseId],
|
|
3585
|
+
payload: { ...selection.payload, consumedByTool: 'devflow_report_step' },
|
|
3586
|
+
createdAt: input.verifiedAt,
|
|
3587
|
+
};
|
|
3588
|
+
this.appendRetrievalLedgerEvent(consumed);
|
|
3589
|
+
this.appendRetrievalLedgerEvent({
|
|
3590
|
+
...consumed,
|
|
3591
|
+
id: (0, retrieval_ledger_1.retrievalLedgerTransitionId)(consumed.id, 'verified', input.verificationReceipt),
|
|
3592
|
+
stage: 'verified',
|
|
3593
|
+
verificationReceipt: `${input.verificationReceipt}:${selection.sourceId}`,
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
return { selected: selected.length, consumed: selected.length, verified: selected.length };
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3312
3599
|
appendTaskIntentArtifact(record) {
|
|
3600
|
+
return this.db.transaction(() => this.appendTaskIntentArtifactInTransaction(record));
|
|
3601
|
+
}
|
|
3602
|
+
appendTaskIntentArtifactInTransaction(record) {
|
|
3313
3603
|
(0, task_semantic_control_1.assertTaskIdentity)(record);
|
|
3314
3604
|
if (!Number.isSafeInteger(record.version) || record.version <= 0) {
|
|
3315
3605
|
throw new Error('TASK_INTENT_INVALID_VERSION');
|
|
@@ -3321,18 +3611,16 @@ class DevFlowDatabase {
|
|
|
3321
3611
|
}
|
|
3322
3612
|
return existingByHash;
|
|
3323
3613
|
}
|
|
3324
|
-
this.db.
|
|
3325
|
-
try {
|
|
3326
|
-
const conflicting = this.db.prepare(`
|
|
3614
|
+
const conflicting = this.db.prepare(`
|
|
3327
3615
|
SELECT * FROM devflow_task_intent_artifacts
|
|
3328
3616
|
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND version = ?
|
|
3329
3617
|
`).get(record.projectRoot, record.sessionId, record.turnId, record.version);
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3618
|
+
if (conflicting)
|
|
3619
|
+
throw new Error(`TASK_INTENT_VERSION_CONFLICT:${record.turnId}:${record.version}`);
|
|
3620
|
+
if (record.supersedesHash && !this.getTaskIntentArtifact(record.supersedesHash)) {
|
|
3621
|
+
throw new Error(`TASK_INTENT_SUPERSEDED_NOT_FOUND:${record.supersedesHash}`);
|
|
3622
|
+
}
|
|
3623
|
+
this.db.prepare(`
|
|
3336
3624
|
INSERT INTO devflow_task_intent_artifacts (
|
|
3337
3625
|
artifact_hash, project_root, project_id, host_id, session_id, turn_id, request_id,
|
|
3338
3626
|
execution_id, version, supersedes_hash, raw_prompt, normalized_prompt, command,
|
|
@@ -3341,17 +3629,7 @@ class DevFlowDatabase {
|
|
|
3341
3629
|
source_hash, created_at
|
|
3342
3630
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3343
3631
|
`).run(record.artifactHash, record.projectRoot, record.projectId, record.hostId, record.sessionId, record.turnId, record.requestId, record.executionId ?? null, record.version, record.supersedesHash ?? null, record.rawPrompt, record.normalizedPrompt, record.command ?? null, (0, task_semantic_control_1.stableSemanticJson)(record.slashArgs), record.activeSkill ?? null, record.intent, record.action, (0, task_semantic_control_1.stableSemanticJson)(record.entities), (0, task_semantic_control_1.stableSemanticJson)(record.targetAnchors), (0, task_semantic_control_1.stableSemanticJson)(record.policyConstraints), (0, task_semantic_control_1.stableSemanticJson)(record.classificationEvidence), (0, task_semantic_control_1.stableSemanticJson)(record.sourceEventIds), record.sourceHash, record.createdAt);
|
|
3344
|
-
|
|
3345
|
-
this.db.exec('COMMIT');
|
|
3346
|
-
return created;
|
|
3347
|
-
}
|
|
3348
|
-
catch (error) {
|
|
3349
|
-
try {
|
|
3350
|
-
this.db.exec('ROLLBACK');
|
|
3351
|
-
}
|
|
3352
|
-
catch { }
|
|
3353
|
-
throw error;
|
|
3354
|
-
}
|
|
3632
|
+
return this.getTaskIntentArtifact(record.artifactHash);
|
|
3355
3633
|
}
|
|
3356
3634
|
getTaskIntentArtifact(artifactHash) {
|
|
3357
3635
|
const row = this.db.prepare(`
|
|
@@ -3386,77 +3664,6 @@ class DevFlowDatabase {
|
|
|
3386
3664
|
`).get(input.projectRoot, input.sessionId, input.sourceHash);
|
|
3387
3665
|
return row ? (0, task_semantic_control_1.mapTaskIntentArtifactRow)(row) : null;
|
|
3388
3666
|
}
|
|
3389
|
-
appendSemanticResolution(record) {
|
|
3390
|
-
(0, semantic_resolution_1.assertSemanticResolutionRecord)(record);
|
|
3391
|
-
const existing = this.getSemanticResolution(record.frameHash);
|
|
3392
|
-
if (existing) {
|
|
3393
|
-
if ((0, semantic_resolution_1.stableSemanticResolutionJson)(existing) !== (0, semantic_resolution_1.stableSemanticResolutionJson)(record)) {
|
|
3394
|
-
throw new Error(`SEMANTIC_RESOLUTION_HASH_CONFLICT:${record.frameHash}`);
|
|
3395
|
-
}
|
|
3396
|
-
return false;
|
|
3397
|
-
}
|
|
3398
|
-
this.db.exec('BEGIN IMMEDIATE');
|
|
3399
|
-
try {
|
|
3400
|
-
const conflicting = this.db.prepare(`
|
|
3401
|
-
SELECT frame_hash FROM devflow_semantic_resolutions
|
|
3402
|
-
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND artifact_revision = ?
|
|
3403
|
-
`).get(record.projectRoot, record.sessionId, record.turnId, record.artifactRevision);
|
|
3404
|
-
if (conflicting) {
|
|
3405
|
-
throw new Error(`SEMANTIC_RESOLUTION_REVISION_CONFLICT:${record.turnId}:${record.artifactRevision}`);
|
|
3406
|
-
}
|
|
3407
|
-
if (record.supersedesFrameHash) {
|
|
3408
|
-
const superseded = this.getSemanticResolution(record.supersedesFrameHash);
|
|
3409
|
-
if (!superseded) {
|
|
3410
|
-
throw new Error(`SEMANTIC_RESOLUTION_SUPERSEDED_NOT_FOUND:${record.supersedesFrameHash}`);
|
|
3411
|
-
}
|
|
3412
|
-
if (superseded.projectRoot !== record.projectRoot
|
|
3413
|
-
|| superseded.sessionId !== record.sessionId
|
|
3414
|
-
|| superseded.turnId !== record.turnId
|
|
3415
|
-
|| superseded.artifactRevision >= record.artifactRevision) {
|
|
3416
|
-
throw new Error('SEMANTIC_RESOLUTION_SUPERSEDES_INVALID');
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3419
|
-
this.db.prepare(`
|
|
3420
|
-
INSERT INTO devflow_semantic_resolutions (
|
|
3421
|
-
frame_hash, project_root, project_id, host_id, session_id, turn_id, request_id,
|
|
3422
|
-
source_hash, artifact_hash, artifact_revision, supersedes_frame_hash, state,
|
|
3423
|
-
generated_by, model_id, model_revision, route_catalog_version, threshold_version,
|
|
3424
|
-
route_json, sampling_json, conflicts_json, frame_json, duration_ms, created_at
|
|
3425
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3426
|
-
`).run(record.frameHash, record.projectRoot, record.projectId, record.hostId, record.sessionId, record.turnId, record.requestId, record.sourceHash, record.artifactHash, record.artifactRevision, record.supersedesFrameHash ?? null, record.state, record.generatedBy, record.modelId ?? null, record.modelRevision ?? null, record.routeCatalogVersion, record.thresholdVersion, (0, semantic_resolution_1.stableSemanticResolutionJson)(record.route), (0, semantic_resolution_1.stableSemanticResolutionJson)(record.sampling), (0, semantic_resolution_1.stableSemanticResolutionJson)(record.conflicts), (0, semantic_resolution_1.stableSemanticResolutionJson)(record.frame), record.durationMs, record.createdAt);
|
|
3427
|
-
this.db.exec('COMMIT');
|
|
3428
|
-
return true;
|
|
3429
|
-
}
|
|
3430
|
-
catch (error) {
|
|
3431
|
-
try {
|
|
3432
|
-
this.db.exec('ROLLBACK');
|
|
3433
|
-
}
|
|
3434
|
-
catch { }
|
|
3435
|
-
throw error;
|
|
3436
|
-
}
|
|
3437
|
-
}
|
|
3438
|
-
getSemanticResolution(frameHash) {
|
|
3439
|
-
const row = this.db.prepare(`
|
|
3440
|
-
SELECT * FROM devflow_semantic_resolutions WHERE frame_hash = ?
|
|
3441
|
-
`).get(frameHash);
|
|
3442
|
-
return row ? (0, semantic_resolution_1.mapSemanticResolutionRow)(row) : null;
|
|
3443
|
-
}
|
|
3444
|
-
getLatestSemanticResolution(projectRoot, sessionId, turnId) {
|
|
3445
|
-
const row = this.db.prepare(`
|
|
3446
|
-
SELECT * FROM devflow_semantic_resolutions
|
|
3447
|
-
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
3448
|
-
ORDER BY artifact_revision DESC, created_at DESC LIMIT 1
|
|
3449
|
-
`).get(projectRoot, sessionId, turnId);
|
|
3450
|
-
return row ? (0, semantic_resolution_1.mapSemanticResolutionRow)(row) : null;
|
|
3451
|
-
}
|
|
3452
|
-
listSemanticResolutions(projectRoot, sessionId, turnId) {
|
|
3453
|
-
return this.db.prepare(`
|
|
3454
|
-
SELECT * FROM devflow_semantic_resolutions
|
|
3455
|
-
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
3456
|
-
ORDER BY artifact_revision ASC, created_at ASC
|
|
3457
|
-
`).all(projectRoot, sessionId, turnId)
|
|
3458
|
-
.map(semantic_resolution_1.mapSemanticResolutionRow);
|
|
3459
|
-
}
|
|
3460
3667
|
listLatestTaskIntentArtifacts(projectRoot, sessionId) {
|
|
3461
3668
|
return this.db.prepare(`
|
|
3462
3669
|
SELECT artifact.* FROM devflow_task_intent_artifacts artifact
|
|
@@ -3660,6 +3867,232 @@ class DevFlowDatabase {
|
|
|
3660
3867
|
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
3661
3868
|
`).run(projectRoot, sessionId, turnId).changes > 0;
|
|
3662
3869
|
}
|
|
3870
|
+
appendTaskAggregate(input) {
|
|
3871
|
+
return this.db.transaction(() => {
|
|
3872
|
+
const existingByKey = this.db.prepare(`
|
|
3873
|
+
SELECT event_json FROM devflow_task_aggregate_events_v2 WHERE idempotency_key = ?
|
|
3874
|
+
`).get(input.event.idempotencyKey);
|
|
3875
|
+
if (existingByKey?.event_json) {
|
|
3876
|
+
const existing = JSON.parse(existingByKey.event_json);
|
|
3877
|
+
if ((0, task_runtime_1.stableTaskRuntimeJson)(existing) !== (0, task_runtime_1.stableTaskRuntimeJson)(input.event)) {
|
|
3878
|
+
throw new Error('TASK_EVENT_IDEMPOTENCY_CONFLICT');
|
|
3879
|
+
}
|
|
3880
|
+
const snapshot = this.getTaskAggregateSnapshot(input.event.identity.projectRoot, input.event.identity.sessionId, input.event.identity.turnId, input.event.revision);
|
|
3881
|
+
if (!snapshot)
|
|
3882
|
+
throw new Error('TASK_AGGREGATE_SNAPSHOT_MISSING');
|
|
3883
|
+
return { event: existing, snapshot, replayed: true };
|
|
3884
|
+
}
|
|
3885
|
+
if (input.definition)
|
|
3886
|
+
this.putTaskDefinitionV2(input.definition, input.event.createdAt);
|
|
3887
|
+
const current = this.getTaskAggregateSnapshot(input.event.identity.projectRoot, input.event.identity.sessionId, input.event.identity.turnId, input.event.revision);
|
|
3888
|
+
const expectedSequence = (current?.lastEventSequence ?? 0) + 1;
|
|
3889
|
+
if (input.event.sequence !== expectedSequence) {
|
|
3890
|
+
throw new Error(`TASK_AGGREGATE_SEQUENCE_CONFLICT:${expectedSequence}->${input.event.sequence}`);
|
|
3891
|
+
}
|
|
3892
|
+
if (input.snapshot.lastEventSequence !== input.event.sequence) {
|
|
3893
|
+
throw new Error('TASK_AGGREGATE_SNAPSHOT_SEQUENCE_MISMATCH');
|
|
3894
|
+
}
|
|
3895
|
+
const identity = input.event.identity;
|
|
3896
|
+
this.db.prepare(`
|
|
3897
|
+
INSERT INTO devflow_task_aggregate_events_v2 (
|
|
3898
|
+
event_id, idempotency_key, project_root, project_id, host_id, session_id,
|
|
3899
|
+
turn_id, request_id, execution_id, revision, sequence, kind, producer,
|
|
3900
|
+
producer_version, causation_id, correlation_id, payload_json, event_json, created_at
|
|
3901
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3902
|
+
`).run(input.event.eventId, input.event.idempotencyKey, identity.projectRoot, identity.projectId, identity.hostId, identity.sessionId, identity.turnId, identity.requestId, identity.executionId ?? null, input.event.revision, input.event.sequence, input.event.kind, input.event.producer, input.event.producerVersion, input.event.causationId ?? null, input.event.correlationId ?? null, (0, task_runtime_1.stableTaskRuntimeJson)(input.event.payload), (0, task_runtime_1.stableTaskRuntimeJson)(input.event), input.event.createdAt);
|
|
3903
|
+
this.db.prepare(`
|
|
3904
|
+
INSERT INTO devflow_task_aggregates_v2 (
|
|
3905
|
+
project_root, session_id, turn_id, revision, definition_hash,
|
|
3906
|
+
activation_snapshot_hash, last_event_sequence, snapshot_json, snapshot_hash, updated_at
|
|
3907
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3908
|
+
ON CONFLICT(project_root, session_id, turn_id, revision) DO UPDATE SET
|
|
3909
|
+
definition_hash=excluded.definition_hash,
|
|
3910
|
+
activation_snapshot_hash=excluded.activation_snapshot_hash,
|
|
3911
|
+
last_event_sequence=excluded.last_event_sequence,
|
|
3912
|
+
snapshot_json=excluded.snapshot_json,
|
|
3913
|
+
snapshot_hash=excluded.snapshot_hash,
|
|
3914
|
+
updated_at=excluded.updated_at
|
|
3915
|
+
`).run(identity.projectRoot, identity.sessionId, identity.turnId, input.event.revision, input.snapshot.taskDefinitionHash, input.snapshot.activationSnapshotHash, input.snapshot.lastEventSequence, (0, task_runtime_1.stableTaskRuntimeJson)(input.snapshot), input.snapshot.snapshotHash, input.snapshot.updatedAt);
|
|
3916
|
+
if (input.outbox) {
|
|
3917
|
+
this.db.prepare(`
|
|
3918
|
+
INSERT INTO devflow_task_outbox_v2 (
|
|
3919
|
+
operation_id, event_id, project_root, session_id, turn_id, revision,
|
|
3920
|
+
kind, payload_json, state, created_at, updated_at
|
|
3921
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)
|
|
3922
|
+
`).run(input.outbox.operationId, input.event.eventId, identity.projectRoot, identity.sessionId, identity.turnId, input.event.revision, input.outbox.kind, (0, task_runtime_1.stableTaskRuntimeJson)(input.outbox.payload), input.event.createdAt, input.event.createdAt);
|
|
3923
|
+
}
|
|
3924
|
+
return { event: input.event, snapshot: input.snapshot, replayed: false };
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
bindRuntimeActivation(input) {
|
|
3928
|
+
return this.db.transaction(() => this.bindRuntimeActivationInTransaction(input));
|
|
3929
|
+
}
|
|
3930
|
+
bindRuntimeActivationInTransaction(input) {
|
|
3931
|
+
const existing = this.getRuntimeActivation(input.identity.projectRoot, input.identity.sessionId, input.identity.turnId, input.revision);
|
|
3932
|
+
if (existing) {
|
|
3933
|
+
if ((0, task_runtime_1.stableTaskRuntimeJson)(existing) !== (0, task_runtime_1.stableTaskRuntimeJson)(input.snapshot)) {
|
|
3934
|
+
throw new Error('RUNTIME_ACTIVATION_CONFLICT');
|
|
3935
|
+
}
|
|
3936
|
+
return { snapshot: existing, replayed: true };
|
|
3937
|
+
}
|
|
3938
|
+
const identity = input.identity;
|
|
3939
|
+
this.db.prepare(`
|
|
3940
|
+
INSERT INTO devflow_runtime_activations_v1 (
|
|
3941
|
+
snapshot_hash, project_root, project_id, host_id, session_id, turn_id,
|
|
3942
|
+
request_id, execution_id, revision, snapshot_json, created_at
|
|
3943
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3944
|
+
`).run(input.snapshot.snapshotHash, identity.projectRoot, identity.projectId, identity.hostId, identity.sessionId, identity.turnId, identity.requestId, identity.executionId ?? null, input.revision, (0, task_runtime_1.stableTaskRuntimeJson)(input.snapshot), input.snapshot.createdAt);
|
|
3945
|
+
return { snapshot: input.snapshot, replayed: false };
|
|
3946
|
+
}
|
|
3947
|
+
getRuntimeActivation(projectRoot, sessionId, turnId, revision) {
|
|
3948
|
+
const row = this.db.prepare(`
|
|
3949
|
+
SELECT snapshot_json FROM devflow_runtime_activations_v1
|
|
3950
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND revision = ?
|
|
3951
|
+
`).get(projectRoot, sessionId, turnId, revision);
|
|
3952
|
+
return row?.snapshot_json ? JSON.parse(row.snapshot_json) : null;
|
|
3953
|
+
}
|
|
3954
|
+
putSemanticExecutionContext(input) {
|
|
3955
|
+
const context = input.context;
|
|
3956
|
+
if (context.plan.sourceFactsHash !== context.facts.factsHash) {
|
|
3957
|
+
throw new Error('SEMANTIC_EXECUTION_CONTEXT_FACTS_PLAN_MISMATCH');
|
|
3958
|
+
}
|
|
3959
|
+
const existing = this.getSemanticExecutionContext(context.identity.projectRoot, context.identity.sessionId, context.identity.turnId, input.revision);
|
|
3960
|
+
if (existing) {
|
|
3961
|
+
if ((0, task_runtime_1.stableTaskRuntimeJson)(existing) !== (0, task_runtime_1.stableTaskRuntimeJson)(context)) {
|
|
3962
|
+
throw new Error('SEMANTIC_EXECUTION_CONTEXT_CONFLICT');
|
|
3963
|
+
}
|
|
3964
|
+
return existing;
|
|
3965
|
+
}
|
|
3966
|
+
this.db.prepare(`INSERT INTO devflow_semantic_execution_contexts (
|
|
3967
|
+
context_hash, project_root, session_id, turn_id, revision, facts_hash,
|
|
3968
|
+
plan_hash, activation_snapshot_hash, definition_hash, context_json, created_at
|
|
3969
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(context.contextHash, context.identity.projectRoot, context.identity.sessionId, context.identity.turnId, input.revision, context.facts.factsHash, context.plan.planHash, context.activationSnapshotHash, context.taskDefinitionHash, (0, task_runtime_1.stableTaskRuntimeJson)(context), context.createdAt);
|
|
3970
|
+
return context;
|
|
3971
|
+
}
|
|
3972
|
+
putCanonicalSemanticTask(input) {
|
|
3973
|
+
const { activation, context, definition } = input;
|
|
3974
|
+
if (input.revision !== definition.revision
|
|
3975
|
+
|| context.taskDefinitionHash !== definition.definitionHash
|
|
3976
|
+
|| definition.semanticContextHash !== context.contextHash
|
|
3977
|
+
|| context.activationSnapshotHash !== activation.snapshotHash
|
|
3978
|
+
|| (0, task_runtime_1.stableTaskRuntimeJson)(context.identity) !== (0, task_runtime_1.stableTaskRuntimeJson)(definition.identity)) {
|
|
3979
|
+
throw new Error('CANONICAL_SEMANTIC_TASK_BINDING_INVALID');
|
|
3980
|
+
}
|
|
3981
|
+
return this.db.transaction(() => {
|
|
3982
|
+
this.bindRuntimeActivationInTransaction({
|
|
3983
|
+
identity: context.identity, revision: input.revision, snapshot: activation,
|
|
3984
|
+
});
|
|
3985
|
+
this.putTaskDefinitionV2(definition, context.createdAt);
|
|
3986
|
+
const persisted = this.putSemanticExecutionContext({ context, revision: input.revision });
|
|
3987
|
+
return persisted;
|
|
3988
|
+
});
|
|
3989
|
+
}
|
|
3990
|
+
getSemanticExecutionContext(projectRoot, sessionId, turnId, revision) {
|
|
3991
|
+
const row = this.db.prepare(`SELECT context_json FROM devflow_semantic_execution_contexts
|
|
3992
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND revision = ?`)
|
|
3993
|
+
.get(projectRoot, sessionId, turnId, revision);
|
|
3994
|
+
return row?.context_json ? JSON.parse(row.context_json) : null;
|
|
3995
|
+
}
|
|
3996
|
+
getSemanticExecutionContextByHash(contextHash) {
|
|
3997
|
+
const row = this.db.prepare(`SELECT context_json FROM devflow_semantic_execution_contexts
|
|
3998
|
+
WHERE context_hash = ?`).get(contextHash);
|
|
3999
|
+
return row?.context_json ? JSON.parse(row.context_json) : null;
|
|
4000
|
+
}
|
|
4001
|
+
getLatestSemanticExecutionContext(input) {
|
|
4002
|
+
const row = this.db.prepare(`SELECT context_json, revision FROM devflow_semantic_execution_contexts
|
|
4003
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ?
|
|
4004
|
+
ORDER BY revision DESC, created_at DESC LIMIT 1`)
|
|
4005
|
+
.get(input.projectRoot, input.sessionId, input.turnId);
|
|
4006
|
+
return row?.context_json ? { context: JSON.parse(row.context_json), revision: Number(row.revision) } : null;
|
|
4007
|
+
}
|
|
4008
|
+
listTaskAggregateEvents(projectRoot, sessionId, turnId, revision) {
|
|
4009
|
+
return this.db.prepare(`
|
|
4010
|
+
SELECT event_json FROM devflow_task_aggregate_events_v2
|
|
4011
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND revision = ?
|
|
4012
|
+
ORDER BY sequence ASC
|
|
4013
|
+
`).all(projectRoot, sessionId, turnId, revision)
|
|
4014
|
+
.map(row => JSON.parse(row.event_json));
|
|
4015
|
+
}
|
|
4016
|
+
getTaskAggregateEventByIdempotencyKey(idempotencyKey) {
|
|
4017
|
+
const row = this.db.prepare(`
|
|
4018
|
+
SELECT event_json FROM devflow_task_aggregate_events_v2 WHERE idempotency_key = ?
|
|
4019
|
+
`).get(idempotencyKey);
|
|
4020
|
+
return row?.event_json ? JSON.parse(row.event_json) : null;
|
|
4021
|
+
}
|
|
4022
|
+
getTaskAggregateSnapshot(projectRoot, sessionId, turnId, revision) {
|
|
4023
|
+
const row = this.db.prepare(`
|
|
4024
|
+
SELECT snapshot_json FROM devflow_task_aggregates_v2
|
|
4025
|
+
WHERE project_root = ? AND session_id = ? AND turn_id = ? AND revision = ?
|
|
4026
|
+
`).get(projectRoot, sessionId, turnId, revision);
|
|
4027
|
+
return row?.snapshot_json ? JSON.parse(row.snapshot_json) : null;
|
|
4028
|
+
}
|
|
4029
|
+
getTaskDefinitionV2(definitionHash) {
|
|
4030
|
+
const row = this.db.prepare(`
|
|
4031
|
+
SELECT definition_json FROM devflow_task_definitions_v2 WHERE definition_hash = ?
|
|
4032
|
+
`).get(definitionHash);
|
|
4033
|
+
return row?.definition_json ? JSON.parse(row.definition_json) : null;
|
|
4034
|
+
}
|
|
4035
|
+
leaseTaskOutbox(input) {
|
|
4036
|
+
if (!input.owner.trim() || input.limit <= 0 || input.leaseMs <= 0)
|
|
4037
|
+
return [];
|
|
4038
|
+
const now = input.now ?? Date.now();
|
|
4039
|
+
return this.db.transaction(() => {
|
|
4040
|
+
const rows = this.db.prepare(`
|
|
4041
|
+
SELECT operation_id FROM devflow_task_outbox_v2
|
|
4042
|
+
WHERE project_root = ? AND (state = 'pending' OR (state = 'leased' AND lease_expires_at <= ?))
|
|
4043
|
+
ORDER BY created_at ASC LIMIT ?
|
|
4044
|
+
`).all(input.projectRoot, now, Math.min(1000, Math.floor(input.limit)));
|
|
4045
|
+
const leased = [];
|
|
4046
|
+
for (const row of rows) {
|
|
4047
|
+
const changed = this.db.prepare(`
|
|
4048
|
+
UPDATE devflow_task_outbox_v2
|
|
4049
|
+
SET state='leased', lease_owner=?, lease_expires_at=?, fencing_token=fencing_token+1,
|
|
4050
|
+
attempts=attempts+1, updated_at=?
|
|
4051
|
+
WHERE operation_id=? AND (state='pending' OR (state='leased' AND lease_expires_at <= ?))
|
|
4052
|
+
`).run(input.owner, now + input.leaseMs, now, row.operation_id, now).changes;
|
|
4053
|
+
if (changed === 1)
|
|
4054
|
+
leased.push(this.getTaskOutbox(row.operation_id));
|
|
4055
|
+
}
|
|
4056
|
+
return leased;
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
completeTaskOutbox(operationId, owner, fencingToken, now = Date.now()) {
|
|
4060
|
+
return this.db.prepare(`
|
|
4061
|
+
UPDATE devflow_task_outbox_v2
|
|
4062
|
+
SET state='completed', lease_owner=NULL, lease_expires_at=NULL, updated_at=?
|
|
4063
|
+
WHERE operation_id=? AND state='leased' AND lease_owner=? AND fencing_token=?
|
|
4064
|
+
`).run(now, operationId, owner, fencingToken).changes === 1;
|
|
4065
|
+
}
|
|
4066
|
+
getTaskOutbox(operationId) {
|
|
4067
|
+
const row = this.db.prepare(`SELECT * FROM devflow_task_outbox_v2 WHERE operation_id = ?`)
|
|
4068
|
+
.get(operationId);
|
|
4069
|
+
if (!row)
|
|
4070
|
+
return null;
|
|
4071
|
+
return {
|
|
4072
|
+
operationId: String(row.operation_id), eventId: String(row.event_id), projectRoot: String(row.project_root),
|
|
4073
|
+
sessionId: String(row.session_id), turnId: String(row.turn_id), revision: Number(row.revision),
|
|
4074
|
+
kind: String(row.kind), payload: JSON.parse(String(row.payload_json)),
|
|
4075
|
+
state: row.state,
|
|
4076
|
+
...(row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {}),
|
|
4077
|
+
...(row.lease_expires_at != null ? { leaseExpiresAt: Number(row.lease_expires_at) } : {}),
|
|
4078
|
+
fencingToken: Number(row.fencing_token), attempts: Number(row.attempts),
|
|
4079
|
+
createdAt: Number(row.created_at), updatedAt: Number(row.updated_at),
|
|
4080
|
+
};
|
|
4081
|
+
}
|
|
4082
|
+
putTaskDefinitionV2(definition, createdAt) {
|
|
4083
|
+
const existing = this.getTaskDefinitionV2(definition.definitionHash);
|
|
4084
|
+
if (existing) {
|
|
4085
|
+
if ((0, task_runtime_1.stableTaskRuntimeJson)(existing) !== (0, task_runtime_1.stableTaskRuntimeJson)(definition)) {
|
|
4086
|
+
throw new Error('TASK_DEFINITION_HASH_CONFLICT');
|
|
4087
|
+
}
|
|
4088
|
+
return;
|
|
4089
|
+
}
|
|
4090
|
+
this.db.prepare(`
|
|
4091
|
+
INSERT INTO devflow_task_definitions_v2 (
|
|
4092
|
+
definition_hash, project_root, session_id, turn_id, revision, definition_json, created_at
|
|
4093
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
4094
|
+
`).run(definition.definitionHash, definition.identity.projectRoot, definition.identity.sessionId, definition.identity.turnId, definition.revision, (0, task_runtime_1.stableTaskRuntimeJson)(definition), createdAt);
|
|
4095
|
+
}
|
|
3663
4096
|
getTerminalTransition(receiptId) {
|
|
3664
4097
|
const row = this.db.prepare(`
|
|
3665
4098
|
SELECT * FROM devflow_terminal_transitions WHERE receipt_id = ?
|
|
@@ -3716,7 +4149,7 @@ class DevFlowDatabase {
|
|
|
3716
4149
|
executionId: row.execution_id ?? undefined, turnId: row.turn_id ?? undefined,
|
|
3717
4150
|
requestId: row.request_id, contextReceipt: row.context_receipt,
|
|
3718
4151
|
schemaVersion: row.schema_version ?? 'retrieval-ledger-event.v1',
|
|
3719
|
-
sourceType: row.source_type, sourceId: row.source_id, stage: row.stage
|
|
4152
|
+
sourceType: row.source_type, sourceId: row.source_id, stage: row.stage,
|
|
3720
4153
|
taskSpecHash: row.task_spec_hash ?? undefined, actor: row.actor ?? undefined,
|
|
3721
4154
|
sourceVersion: row.source_version ?? undefined, sourceContentHash: row.source_content_hash ?? undefined,
|
|
3722
4155
|
evidenceIds: parseJsonStringArray(row.evidence_ids), reasonCode: row.reason_code ?? undefined,
|
|
@@ -4705,3 +5138,28 @@ function sameTaskIdentity(left, right) {
|
|
|
4705
5138
|
&& left.requestId === right.requestId
|
|
4706
5139
|
&& left.executionId === right.executionId;
|
|
4707
5140
|
}
|
|
5141
|
+
function mapRetrievalSessionV2(row) {
|
|
5142
|
+
const executionId = typeof row.execution_id === 'string' ? row.execution_id.trim() : '';
|
|
5143
|
+
if (!executionId)
|
|
5144
|
+
throw new Error('RETRIEVAL_SESSION_IDENTITY_INVALID:execution_id');
|
|
5145
|
+
return {
|
|
5146
|
+
id: String(row.id),
|
|
5147
|
+
identity: {
|
|
5148
|
+
projectRoot: String(row.project_root), projectId: String(row.project_id),
|
|
5149
|
+
hostId: String(row.host_id), sessionId: String(row.task_session_id),
|
|
5150
|
+
turnId: String(row.turn_id), requestId: String(row.request_id),
|
|
5151
|
+
executionId,
|
|
5152
|
+
},
|
|
5153
|
+
taskRevision: Number(row.task_revision),
|
|
5154
|
+
factsHash: String(row.facts_hash),
|
|
5155
|
+
planHash: String(row.plan_hash),
|
|
5156
|
+
activationSnapshotHash: String(row.activation_snapshot_hash),
|
|
5157
|
+
coordinatorVersion: String(row.coordinator_version),
|
|
5158
|
+
state: row.state,
|
|
5159
|
+
cycle: Number(row.cycle), noGainCount: Number(row.no_gain_count), maxCycles: 3,
|
|
5160
|
+
initialTokenBudget: Number(row.initial_token_budget),
|
|
5161
|
+
remainingTokenBudget: Number(row.remaining_token_budget),
|
|
5162
|
+
...(row.final_receipt_hash ? { finalReceiptHash: String(row.final_receipt_hash) } : {}),
|
|
5163
|
+
createdAt: Number(row.created_at), updatedAt: Number(row.updated_at), expiresAt: Number(row.expires_at),
|
|
5164
|
+
};
|
|
5165
|
+
}
|