@devflow-tools/database 0.17.7 → 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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/data/LocalDataProvider.d.ts +63 -0
  4. package/dist/data/LocalDataProvider.js +332 -0
  5. package/dist/data/default-enforcer-rules.d.ts +3 -0
  6. package/dist/data/default-enforcer-rules.js +170 -0
  7. package/dist/data/devflow-schema.d.ts +2 -0
  8. package/dist/data/devflow-schema.js +80 -0
  9. package/dist/database.d.ts +45 -7
  10. package/dist/database.js +583 -125
  11. package/dist/index.d.ts +8 -4
  12. package/dist/index.js +11 -5
  13. package/dist/retrieval-ledger.d.ts +16 -1
  14. package/dist/retrieval-ledger.js +6 -0
  15. package/dist/retrieval-runtime.d.ts +39 -0
  16. package/dist/retrieval-runtime.js +2 -0
  17. package/dist/task-aggregate.d.ts +49 -0
  18. package/dist/task-aggregate.js +2 -0
  19. package/dist/task-semantic-control.d.ts +8 -1
  20. package/dist/task-semantic-control.js +2 -4
  21. package/package.json +15 -3
  22. package/CHANGELOG.md +0 -836
  23. package/__tests__/database.failure-category.test.ts +0 -44
  24. package/__tests__/database.host-actions.test.ts +0 -405
  25. package/__tests__/database.learning-candidates.test.ts +0 -93
  26. package/__tests__/database.memory-turn-receipts.test.ts +0 -98
  27. package/__tests__/database.retrieval-ledger.test.ts +0 -68
  28. package/__tests__/database.retrieval-sessions.test.ts +0 -79
  29. package/__tests__/database.semantic-resolution.test.ts +0 -87
  30. package/__tests__/database.skill-executions.test.ts +0 -456
  31. package/__tests__/database.task-runtime.test.ts +0 -73
  32. package/__tests__/database.test.ts +0 -177
  33. package/__tests__/database.work-queue.test.ts +0 -274
  34. package/__tests__/database.workflow-workers.test.ts +0 -60
  35. package/__tests__/node-sqlite.test.ts +0 -68
  36. package/src/database.ts +0 -5853
  37. package/src/host-actions.ts +0 -211
  38. package/src/index.ts +0 -153
  39. package/src/learning-candidates.ts +0 -181
  40. package/src/node-sqlite.ts +0 -60
  41. package/src/obligation-ledger.ts +0 -57
  42. package/src/retrieval-ledger.ts +0 -35
  43. package/src/retrieval-sessions.ts +0 -196
  44. package/src/semantic-resolution.ts +0 -181
  45. package/src/task-runtime.ts +0 -169
  46. package/src/task-semantic-control.ts +0 -270
  47. package/src/types.ts +0 -17
  48. package/src/work-queue.ts +0 -123
  49. package/src/workflow-workers.ts +0 -198
  50. package/tsconfig.json +0 -19
  51. package/tsconfig.tsbuildinfo +0 -1
  52. 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 semantic_resolution_1 = require("./semantic-resolution");
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
- const stateDir = process.env.DEVFLOW_STATE_DIR ?? (0, path_1.join)(home, '.devflow', 'global');
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 ((event.stage === 'selected' || event.stage === 'exposed')
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.exec('BEGIN IMMEDIATE');
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
- if (conflicting)
3331
- throw new Error(`TASK_INTENT_VERSION_CONFLICT:${record.turnId}:${record.version}`);
3332
- if (record.supersedesHash && !this.getTaskIntentArtifact(record.supersedesHash)) {
3333
- throw new Error(`TASK_INTENT_SUPERSEDED_NOT_FOUND:${record.supersedesHash}`);
3334
- }
3335
- this.db.prepare(`
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
- const created = this.getTaskIntentArtifact(record.artifactHash);
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 === 'adopted' ? 'consumed' : 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
+ }