@devflow-tools/database 0.17.2 → 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/database.js CHANGED
@@ -15,6 +15,7 @@ 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
17
  const task_semantic_control_1 = require("./task-semantic-control");
18
+ const task_runtime_1 = require("./task-runtime");
18
19
  const CONTEXT_REQUIRED_SKILLS = new Set([
19
20
  'react', 'vue', 'nest', 'nextjs', 'graphql', 'typescript',
20
21
  ]);
@@ -356,6 +357,9 @@ class DevFlowDatabase {
356
357
  source_id TEXT NOT NULL, stage TEXT NOT NULL, rank INTEGER, raw_score REAL,
357
358
  normalized_score REAL, final_score REAL, applicability TEXT NOT NULL DEFAULT '[]',
358
359
  reason TEXT, tool_evidence TEXT NOT NULL DEFAULT '[]', verification_receipt TEXT,
360
+ schema_version TEXT NOT NULL DEFAULT 'retrieval-ledger-event.v1', task_spec_hash TEXT,
361
+ actor TEXT, source_version TEXT, source_content_hash TEXT,
362
+ evidence_ids TEXT NOT NULL DEFAULT '[]', reason_code TEXT,
359
363
  payload TEXT NOT NULL DEFAULT '{}', created_at INTEGER NOT NULL
360
364
  );
361
365
  CREATE INDEX IF NOT EXISTS idx_retrieval_ledger_identity ON devflow_retrieval_ledger(
@@ -460,6 +464,40 @@ class DevFlowDatabase {
460
464
  CREATE INDEX IF NOT EXISTS idx_terminal_transition_turn
461
465
  ON devflow_terminal_transitions(project_root, session_id, turn_id, sequence);
462
466
 
467
+ CREATE TABLE IF NOT EXISTS devflow_task_runtime_events (
468
+ event_id TEXT PRIMARY KEY,
469
+ schema_version TEXT NOT NULL,
470
+ producer TEXT NOT NULL,
471
+ producer_version TEXT NOT NULL,
472
+ project_root TEXT NOT NULL,
473
+ project_id TEXT NOT NULL,
474
+ host_id TEXT NOT NULL,
475
+ session_id TEXT NOT NULL,
476
+ turn_id TEXT NOT NULL,
477
+ request_id TEXT NOT NULL,
478
+ execution_id TEXT,
479
+ task_spec_hash TEXT NOT NULL,
480
+ sequence INTEGER NOT NULL CHECK(sequence > 0),
481
+ kind TEXT NOT NULL,
482
+ payload_json TEXT NOT NULL DEFAULT '{}',
483
+ created_at INTEGER NOT NULL,
484
+ UNIQUE(project_root, session_id, turn_id, sequence)
485
+ );
486
+ CREATE INDEX IF NOT EXISTS idx_task_runtime_event_identity
487
+ ON devflow_task_runtime_events(project_root, session_id, turn_id, sequence);
488
+
489
+ CREATE TABLE IF NOT EXISTS devflow_task_runtime_snapshots (
490
+ project_root TEXT NOT NULL,
491
+ session_id TEXT NOT NULL,
492
+ turn_id TEXT NOT NULL,
493
+ last_event_sequence INTEGER NOT NULL,
494
+ schema_version TEXT NOT NULL,
495
+ snapshot_json TEXT NOT NULL,
496
+ snapshot_hash TEXT NOT NULL,
497
+ updated_at INTEGER NOT NULL,
498
+ PRIMARY KEY(project_root, session_id, turn_id)
499
+ );
500
+
463
501
  CREATE TABLE IF NOT EXISTS devflow_transcript_checkpoints (
464
502
  id TEXT PRIMARY KEY,
465
503
  project_root TEXT NOT NULL,
@@ -613,9 +651,11 @@ class DevFlowDatabase {
613
651
  project_root TEXT NOT NULL,
614
652
  session_id TEXT,
615
653
  turn_id TEXT,
654
+ source_hash TEXT,
655
+ task_spec_hash TEXT,
616
656
  payload TEXT NOT NULL,
617
657
  state TEXT NOT NULL DEFAULT 'pending'
618
- CHECK(state IN ('pending', 'leased', 'completed', 'failed', 'dead_letter')),
658
+ CHECK(state IN ('pending', 'leased', 'completed', 'failed', 'dead_letter', 'cancelled')),
619
659
  attempts INTEGER NOT NULL DEFAULT 0 CHECK(attempts >= 0),
620
660
  max_attempts INTEGER NOT NULL DEFAULT 5 CHECK(max_attempts > 0),
621
661
  lease_owner TEXT,
@@ -625,7 +665,11 @@ class DevFlowDatabase {
625
665
  error_message TEXT,
626
666
  created_at INTEGER NOT NULL,
627
667
  updated_at INTEGER NOT NULL,
628
- completed_at INTEGER
668
+ completed_at INTEGER,
669
+ cancelled_at INTEGER,
670
+ cancellation_reason TEXT,
671
+ replay_of_id TEXT,
672
+ replay_count INTEGER NOT NULL DEFAULT 0
629
673
  );
630
674
 
631
675
  CREATE INDEX IF NOT EXISTS idx_work_items_ready
@@ -635,6 +679,16 @@ class DevFlowDatabase {
635
679
  CREATE INDEX IF NOT EXISTS idx_work_items_completed
636
680
  ON devflow_work_items(project_root, completed_at DESC);
637
681
 
682
+ CREATE TABLE IF NOT EXISTS devflow_project_mutation_leases (
683
+ project_root TEXT NOT NULL,
684
+ mutation_kind TEXT NOT NULL,
685
+ owner TEXT NOT NULL,
686
+ lease_expires_at INTEGER NOT NULL,
687
+ source_hash TEXT,
688
+ updated_at INTEGER NOT NULL,
689
+ PRIMARY KEY(project_root, mutation_kind)
690
+ );
691
+
638
692
  CREATE TABLE IF NOT EXISTS devflow_session_closures (
639
693
  session_id TEXT NOT NULL,
640
694
  project_root TEXT NOT NULL,
@@ -978,6 +1032,58 @@ class DevFlowDatabase {
978
1032
  this.db.exec('ALTER TABLE devflow_context_receipts ADD COLUMN request_id TEXT');
979
1033
  }
980
1034
  catch { }
1035
+ try {
1036
+ this.db.exec("ALTER TABLE devflow_retrieval_ledger ADD COLUMN schema_version TEXT NOT NULL DEFAULT 'retrieval-ledger-event.v1'");
1037
+ }
1038
+ catch { }
1039
+ try {
1040
+ this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN task_spec_hash TEXT');
1041
+ }
1042
+ catch { }
1043
+ try {
1044
+ this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN actor TEXT');
1045
+ }
1046
+ catch { }
1047
+ try {
1048
+ this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN source_version TEXT');
1049
+ }
1050
+ catch { }
1051
+ try {
1052
+ this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN source_content_hash TEXT');
1053
+ }
1054
+ catch { }
1055
+ try {
1056
+ this.db.exec("ALTER TABLE devflow_retrieval_ledger ADD COLUMN evidence_ids TEXT NOT NULL DEFAULT '[]'");
1057
+ }
1058
+ catch { }
1059
+ try {
1060
+ this.db.exec('ALTER TABLE devflow_retrieval_ledger ADD COLUMN reason_code TEXT');
1061
+ }
1062
+ catch { }
1063
+ try {
1064
+ this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN source_hash TEXT');
1065
+ }
1066
+ catch { }
1067
+ try {
1068
+ this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN task_spec_hash TEXT');
1069
+ }
1070
+ catch { }
1071
+ try {
1072
+ this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN cancelled_at INTEGER');
1073
+ }
1074
+ catch { }
1075
+ try {
1076
+ this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN cancellation_reason TEXT');
1077
+ }
1078
+ catch { }
1079
+ try {
1080
+ this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN replay_of_id TEXT');
1081
+ }
1082
+ catch { }
1083
+ try {
1084
+ this.db.exec('ALTER TABLE devflow_work_items ADD COLUMN replay_count INTEGER NOT NULL DEFAULT 0');
1085
+ }
1086
+ catch { }
981
1087
  this.db.exec('UPDATE devflow_rules SET updated_at = created_at WHERE updated_at IS NULL');
982
1088
  this.db.exec('CREATE INDEX IF NOT EXISTS idx_devflow_rules_gate ON devflow_rules(gate, enabled)');
983
1089
  // Migration: tool_metrics table for per-tool metrics collection
@@ -2412,7 +2518,8 @@ class DevFlowDatabase {
2412
2518
  throw new Error('Work idempotency key is required');
2413
2519
  if (!input.projectRoot.trim())
2414
2520
  throw new Error('Work project root is required');
2415
- const maxAttempts = input.maxAttempts ?? 5;
2521
+ const maxAttempts = input.maxAttempts
2522
+ ?? (input.kind === 'knowledge.ingest' || input.kind === 'knowledge.index_refresh' ? 3 : 5);
2416
2523
  if (!Number.isSafeInteger(maxAttempts) || maxAttempts <= 0) {
2417
2524
  throw new Error('Work maxAttempts must be a positive safe integer');
2418
2525
  }
@@ -2423,10 +2530,10 @@ class DevFlowDatabase {
2423
2530
  }
2424
2531
  const row = this.db.prepare(`
2425
2532
  INSERT INTO devflow_work_items (
2426
- id, idempotency_key, kind, project_root, session_id, turn_id, payload,
2533
+ id, idempotency_key, kind, project_root, session_id, turn_id, source_hash, task_spec_hash, payload,
2427
2534
  state, attempts, max_attempts, lease_owner, lease_expires_at,
2428
2535
  next_attempt_at, error_category, error_message, created_at, updated_at, completed_at
2429
- ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, ?, NULL, NULL, ?, ?, NULL)
2536
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, ?, NULL, NULL, ?, ?, NULL)
2430
2537
  ON CONFLICT(idempotency_key) DO UPDATE SET
2431
2538
  state = CASE
2432
2539
  WHEN devflow_work_items.state = 'dead_letter'
@@ -2460,13 +2567,58 @@ class DevFlowDatabase {
2460
2567
  ELSE devflow_work_items.updated_at
2461
2568
  END
2462
2569
  RETURNING *
2463
- `).get((0, crypto_1.randomUUID)(), input.idempotencyKey, input.kind, input.projectRoot, input.sessionId ?? null, input.turnId ?? null, JSON.stringify(input.payload ?? null), maxAttempts, nextAttemptAt, createdAt, createdAt);
2570
+ `).get((0, crypto_1.randomUUID)(), input.idempotencyKey, input.kind, input.projectRoot, input.sessionId ?? null, input.turnId ?? null, input.sourceHash ?? null, input.taskSpecHash ?? null, JSON.stringify(input.payload ?? null), maxAttempts, nextAttemptAt, createdAt, createdAt);
2464
2571
  return this.mapWorkItem(row);
2465
2572
  }
2466
2573
  getWorkByIdempotencyKey(idempotencyKey) {
2467
2574
  const row = this.db.prepare('SELECT * FROM devflow_work_items WHERE idempotency_key = ?').get(idempotencyKey);
2468
2575
  return row ? this.mapWorkItem(row) : null;
2469
2576
  }
2577
+ getWorkById(id) {
2578
+ const row = this.db.prepare('SELECT * FROM devflow_work_items WHERE id = ?').get(id);
2579
+ return row ? this.mapWorkItem(row) : null;
2580
+ }
2581
+ acquireProjectMutationLease(input) {
2582
+ const now = input.now ?? Date.now();
2583
+ return this.db.prepare(`
2584
+ INSERT INTO devflow_project_mutation_leases
2585
+ (project_root, mutation_kind, owner, lease_expires_at, source_hash, updated_at)
2586
+ VALUES (?, ?, ?, ?, ?, ?)
2587
+ ON CONFLICT(project_root, mutation_kind) DO UPDATE SET
2588
+ owner = excluded.owner, lease_expires_at = excluded.lease_expires_at,
2589
+ source_hash = excluded.source_hash, updated_at = excluded.updated_at
2590
+ WHERE devflow_project_mutation_leases.lease_expires_at <= ? OR devflow_project_mutation_leases.owner = excluded.owner
2591
+ `).run(input.projectRoot, input.mutationKind, input.owner, now + input.leaseMs, input.sourceHash ?? null, now, now).changes > 0;
2592
+ }
2593
+ releaseProjectMutationLease(projectRoot, mutationKind, owner) {
2594
+ return this.db.prepare('DELETE FROM devflow_project_mutation_leases WHERE project_root = ? AND mutation_kind = ? AND owner = ?')
2595
+ .run(projectRoot, mutationKind, owner).changes > 0;
2596
+ }
2597
+ cancelStaleWork(input) {
2598
+ const current = this.getWorkById(input.id);
2599
+ const stale = current && current.state === 'leased' && current.leaseOwner === input.owner
2600
+ && ((input.sourceHash !== undefined && current.sourceHash !== input.sourceHash)
2601
+ || (input.taskSpecHash !== undefined && current.taskSpecHash !== input.taskSpecHash));
2602
+ if (!stale)
2603
+ return false;
2604
+ const now = input.now ?? Date.now();
2605
+ try {
2606
+ return this.db.prepare(`UPDATE devflow_work_items SET state = 'cancelled', lease_owner = NULL, lease_expires_at = NULL, cancelled_at = ?, cancellation_reason = ?, updated_at = ? WHERE id = ? AND state = 'leased' AND lease_owner = ?`)
2607
+ .run(now, input.reason ?? 'stale_source_or_task_spec', now, input.id, input.owner).changes === 1;
2608
+ }
2609
+ catch {
2610
+ return this.db.prepare(`UPDATE devflow_work_items SET state = 'completed', lease_owner = NULL, lease_expires_at = NULL, cancelled_at = ?, cancellation_reason = ?, updated_at = ? WHERE id = ? AND state = 'leased' AND lease_owner = ?`)
2611
+ .run(now, input.reason ?? 'stale_source_or_task_spec', now, input.id, input.owner).changes === 1;
2612
+ }
2613
+ }
2614
+ replayDeadLetterWork(id, input) {
2615
+ const original = this.getWorkById(id);
2616
+ if (!original || original.state !== 'dead_letter')
2617
+ return null;
2618
+ const replay = this.enqueueWork({ idempotencyKey: input.idempotencyKey, kind: original.kind, projectRoot: original.projectRoot, sessionId: original.sessionId, turnId: original.turnId, payload: original.payload, sourceHash: input.sourceHash ?? original.sourceHash, taskSpecHash: input.taskSpecHash ?? original.taskSpecHash, maxAttempts: original.maxAttempts, nextAttemptAt: input.now ?? Date.now() });
2619
+ this.db.prepare('UPDATE devflow_work_items SET replay_of_id = ?, replay_count = replay_count + 1 WHERE id = ?').run(original.id, replay.id);
2620
+ return this.getWorkById(replay.id);
2621
+ }
2470
2622
  requestSessionClosure(input) {
2471
2623
  if (!input.sessionId.trim())
2472
2624
  throw new Error('Session closure requires a session ID');
@@ -2718,8 +2870,10 @@ class DevFlowDatabase {
2718
2870
  projectRoot: row.project_root,
2719
2871
  sessionId: row.session_id ?? undefined,
2720
2872
  turnId: row.turn_id ?? undefined,
2873
+ sourceHash: row.source_hash ?? undefined,
2874
+ taskSpecHash: row.task_spec_hash ?? undefined,
2721
2875
  payload: parseJson(row.payload),
2722
- state: row.state,
2876
+ state: row.cancelled_at != null ? 'cancelled' : row.state,
2723
2877
  attempts: Number(row.attempts),
2724
2878
  maxAttempts: Number(row.max_attempts),
2725
2879
  leaseOwner: row.lease_owner ?? undefined,
@@ -2730,6 +2884,10 @@ class DevFlowDatabase {
2730
2884
  createdAt: Number(row.created_at),
2731
2885
  updatedAt: Number(row.updated_at),
2732
2886
  completedAt: row.completed_at == null ? undefined : Number(row.completed_at),
2887
+ cancelledAt: row.cancelled_at == null ? undefined : Number(row.cancelled_at),
2888
+ cancellationReason: row.cancellation_reason ?? undefined,
2889
+ replayOfId: row.replay_of_id ?? undefined,
2890
+ replayCount: Number(row.replay_count ?? 0),
2733
2891
  };
2734
2892
  }
2735
2893
  mapSessionClosure(row) {
@@ -3070,6 +3228,33 @@ class DevFlowDatabase {
3070
3228
  requestId: row.request_id ?? undefined,
3071
3229
  } : null;
3072
3230
  }
3231
+ getContextReceiptForRequest(projectRoot, sessionId, requestId) {
3232
+ const row = this.db.prepare(`
3233
+ SELECT project_root, session_id, execution_id, context_hash, issued_at, expires_at,
3234
+ selected_files, memory_ids, canonical_next_action, canonical_action_json,
3235
+ action_attempts, action_satisfied_at, action_degradation_json, request_id
3236
+ FROM devflow_context_receipts
3237
+ WHERE project_root = ? AND session_id = ? AND request_id = ?
3238
+ ORDER BY issued_at DESC
3239
+ LIMIT 1
3240
+ `).get(projectRoot, sessionId, requestId);
3241
+ return row ? {
3242
+ projectRoot: row.project_root,
3243
+ sessionId: row.session_id,
3244
+ executionId: row.execution_id,
3245
+ contextHash: row.context_hash,
3246
+ issuedAt: row.issued_at,
3247
+ expiresAt: row.expires_at,
3248
+ selectedFiles: parseJsonStringArray(row.selected_files),
3249
+ memoryIds: parseJsonStringArray(row.memory_ids),
3250
+ canonicalNextAction: row.canonical_next_action ?? undefined,
3251
+ canonicalAction: parseJsonObject(row.canonical_action_json),
3252
+ actionAttempts: parseJsonArray(row.action_attempts),
3253
+ actionSatisfiedAt: row.action_satisfied_at ?? undefined,
3254
+ actionDegradation: parseJsonObject(row.action_degradation_json),
3255
+ requestId: row.request_id ?? undefined,
3256
+ } : null;
3257
+ }
3073
3258
  recordContextSelectionEvent(event) {
3074
3259
  return this.db.prepare(`
3075
3260
  INSERT OR IGNORE INTO devflow_context_selection_events
@@ -3090,8 +3275,9 @@ class DevFlowDatabase {
3090
3275
  id, project_root, session_id, execution_id, turn_id, request_id, context_receipt,
3091
3276
  source_type, source_id, stage, rank, raw_score, normalized_score, final_score,
3092
3277
  applicability, reason, tool_evidence, verification_receipt, payload, created_at
3093
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3094
- `).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).changes > 0;
3278
+ , schema_version, task_spec_hash, actor, source_version, source_content_hash, evidence_ids, reason_code
3279
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3280
+ `).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;
3095
3281
  }
3096
3282
  appendTaskIntentArtifact(record) {
3097
3283
  (0, task_semantic_control_1.assertTaskIdentity)(record);
@@ -3281,6 +3467,98 @@ class DevFlowDatabase {
3281
3467
  `).run(record.receiptId, record.projectRoot, record.projectId, record.hostId, record.sessionId, record.turnId, record.requestId, record.executionId ?? null, record.sequence, record.fromState ?? null, record.toState, record.sourceReceiptId ?? null, record.reason, (0, task_semantic_control_1.stableSemanticJson)(record.payload), record.createdAt);
3282
3468
  return this.getTerminalTransition(record.receiptId);
3283
3469
  }
3470
+ appendTaskRuntimeEvent(event) {
3471
+ const existing = this.getTaskRuntimeEvent(event.eventId);
3472
+ if (existing) {
3473
+ if ((0, task_runtime_1.stableTaskRuntimeJson)(existing) !== (0, task_runtime_1.stableTaskRuntimeJson)(event)) {
3474
+ throw new Error(`TASK_RUNTIME_EVENT_CONFLICT:${event.eventId}`);
3475
+ }
3476
+ return existing;
3477
+ }
3478
+ const atSequence = this.db.prepare(`
3479
+ SELECT event_id FROM devflow_task_runtime_events
3480
+ WHERE project_root = ? AND session_id = ? AND turn_id = ? AND sequence = ?
3481
+ `).get(event.identity.projectRoot, event.identity.sessionId, event.identity.turnId, event.sequence);
3482
+ if (atSequence)
3483
+ throw new Error(`TASK_RUNTIME_SEQUENCE_CONFLICT:${event.sequence}`);
3484
+ try {
3485
+ this.db.prepare(`
3486
+ INSERT INTO devflow_task_runtime_events (
3487
+ event_id, schema_version, producer, producer_version, project_root, project_id,
3488
+ host_id, session_id, turn_id, request_id, execution_id, task_spec_hash,
3489
+ sequence, kind, payload_json, created_at
3490
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3491
+ `).run(event.eventId, event.schemaVersion, event.producer, event.producerVersion, event.identity.projectRoot, event.identity.projectId, event.identity.hostId, event.identity.sessionId, event.identity.turnId, event.identity.requestId, event.identity.executionId ?? null, event.taskSpecHash, event.sequence, event.kind, (0, task_runtime_1.stableTaskRuntimeJson)(event.payload), event.createdAt);
3492
+ }
3493
+ catch (error) {
3494
+ const concurrentEvent = this.getTaskRuntimeEvent(event.eventId);
3495
+ if (concurrentEvent) {
3496
+ if ((0, task_runtime_1.stableTaskRuntimeJson)(concurrentEvent) === (0, task_runtime_1.stableTaskRuntimeJson)(event))
3497
+ return concurrentEvent;
3498
+ throw new Error(`TASK_RUNTIME_EVENT_CONFLICT:${event.eventId}`);
3499
+ }
3500
+ const concurrentSequence = this.db.prepare(`
3501
+ SELECT event_id FROM devflow_task_runtime_events
3502
+ WHERE project_root = ? AND session_id = ? AND turn_id = ? AND sequence = ?
3503
+ `).get(event.identity.projectRoot, event.identity.sessionId, event.identity.turnId, event.sequence);
3504
+ if (concurrentSequence)
3505
+ throw new Error(`TASK_RUNTIME_SEQUENCE_CONFLICT:${event.sequence}`);
3506
+ throw error;
3507
+ }
3508
+ return this.getTaskRuntimeEvent(event.eventId);
3509
+ }
3510
+ getTaskRuntimeEvent(eventId) {
3511
+ const row = this.db.prepare('SELECT * FROM devflow_task_runtime_events WHERE event_id = ?')
3512
+ .get(eventId);
3513
+ return row ? (0, task_runtime_1.mapTaskRuntimeEventRow)(row) : null;
3514
+ }
3515
+ listTaskRuntimeEvents(projectRoot, sessionId, turnId) {
3516
+ return this.db.prepare(`
3517
+ SELECT * FROM devflow_task_runtime_events
3518
+ WHERE project_root = ? AND session_id = ? AND turn_id = ?
3519
+ ORDER BY sequence ASC
3520
+ `).all(projectRoot, sessionId, turnId)
3521
+ .map(task_runtime_1.mapTaskRuntimeEventRow);
3522
+ }
3523
+ putTaskRuntimeSnapshot(snapshot) {
3524
+ const current = this.getTaskRuntimeSnapshot(snapshot.identity.projectRoot, snapshot.identity.sessionId, snapshot.identity.turnId);
3525
+ if (current && current.lastEventSequence > snapshot.lastEventSequence) {
3526
+ throw new Error('TASK_RUNTIME_SNAPSHOT_REGRESSION');
3527
+ }
3528
+ this.db.prepare(`
3529
+ INSERT INTO devflow_task_runtime_snapshots (
3530
+ project_root, session_id, turn_id, last_event_sequence, schema_version,
3531
+ snapshot_json, snapshot_hash, updated_at
3532
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3533
+ ON CONFLICT(project_root, session_id, turn_id) DO UPDATE SET
3534
+ last_event_sequence = excluded.last_event_sequence,
3535
+ schema_version = excluded.schema_version,
3536
+ snapshot_json = excluded.snapshot_json,
3537
+ snapshot_hash = excluded.snapshot_hash,
3538
+ updated_at = excluded.updated_at
3539
+ WHERE excluded.last_event_sequence >= devflow_task_runtime_snapshots.last_event_sequence
3540
+ `).run(snapshot.identity.projectRoot, snapshot.identity.sessionId, snapshot.identity.turnId, snapshot.lastEventSequence, snapshot.schemaVersion, (0, task_runtime_1.stableTaskRuntimeJson)(snapshot), snapshot.snapshotHash, snapshot.updatedAt);
3541
+ return this.getTaskRuntimeSnapshot(snapshot.identity.projectRoot, snapshot.identity.sessionId, snapshot.identity.turnId);
3542
+ }
3543
+ appendTaskRuntimeEventAndSnapshot(event, snapshot) {
3544
+ return this.db.transaction(() => {
3545
+ this.appendTaskRuntimeEvent(event);
3546
+ return this.putTaskRuntimeSnapshot(snapshot);
3547
+ });
3548
+ }
3549
+ getTaskRuntimeSnapshot(projectRoot, sessionId, turnId) {
3550
+ const row = this.db.prepare(`
3551
+ SELECT * FROM devflow_task_runtime_snapshots
3552
+ WHERE project_root = ? AND session_id = ? AND turn_id = ?
3553
+ `).get(projectRoot, sessionId, turnId);
3554
+ return row ? (0, task_runtime_1.mapTaskRuntimeSnapshotRow)(row) : null;
3555
+ }
3556
+ deleteTaskRuntimeSnapshot(projectRoot, sessionId, turnId) {
3557
+ return this.db.prepare(`
3558
+ DELETE FROM devflow_task_runtime_snapshots
3559
+ WHERE project_root = ? AND session_id = ? AND turn_id = ?
3560
+ `).run(projectRoot, sessionId, turnId).changes > 0;
3561
+ }
3284
3562
  getTerminalTransition(receiptId) {
3285
3563
  const row = this.db.prepare(`
3286
3564
  SELECT * FROM devflow_terminal_transitions WHERE receipt_id = ?
@@ -3336,7 +3614,11 @@ class DevFlowDatabase {
3336
3614
  id: row.id, projectRoot: row.project_root, sessionId: row.session_id,
3337
3615
  executionId: row.execution_id ?? undefined, turnId: row.turn_id ?? undefined,
3338
3616
  requestId: row.request_id, contextReceipt: row.context_receipt,
3339
- sourceType: row.source_type, sourceId: row.source_id, stage: row.stage,
3617
+ schemaVersion: row.schema_version ?? 'retrieval-ledger-event.v1',
3618
+ sourceType: row.source_type, sourceId: row.source_id, stage: row.stage === 'adopted' ? 'consumed' : row.stage,
3619
+ taskSpecHash: row.task_spec_hash ?? undefined, actor: row.actor ?? undefined,
3620
+ sourceVersion: row.source_version ?? undefined, sourceContentHash: row.source_content_hash ?? undefined,
3621
+ evidenceIds: parseJsonStringArray(row.evidence_ids), reasonCode: row.reason_code ?? undefined,
3340
3622
  rank: row.rank ?? undefined, rawScore: row.raw_score ?? undefined,
3341
3623
  normalizedScore: row.normalized_score ?? undefined, finalScore: row.final_score ?? undefined,
3342
3624
  applicability: parseJsonStringArray(row.applicability), reason: row.reason ?? undefined,
@@ -3572,10 +3854,10 @@ class DevFlowDatabase {
3572
3854
  const turnId = (0, obligation_ledger_1.normalizeTurnId)(input.turnId);
3573
3855
  this.db.prepare(`
3574
3856
  UPDATE devflow_memory_turns
3575
- SET status = 'skipped', receipt_id = ?, memory_ids = '[]', source = 'host_skip',
3857
+ SET status = 'skipped', receipt_id = ?, memory_ids = '[]', source = ?,
3576
3858
  reason = ?, decided_at = ?
3577
3859
  WHERE turn_id = ? AND status = 'pending'
3578
- `).run(input.receiptId, input.reason, input.decidedAt ?? Date.now(), turnId);
3860
+ `).run(input.receiptId, input.source ?? 'host_skip', input.reason, input.decidedAt ?? Date.now(), turnId);
3579
3861
  const turn = this.getMemoryTurn(turnId);
3580
3862
  if (!turn || turn.status !== 'skipped') {
3581
3863
  throw new Error(`Memory turn ${turnId} is not pending or does not exist`);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { DevFlowDatabase, getGlobalDevFlowDbPath, openGlobalDevFlowDatabase, openGlobalDevFlowReadOnlyDatabase, } from './database';
2
2
  export type { BenchmarkReportMetaRecord, BenchmarkReportRecord, FeedbackRecord, GovernanceAuditRecord, GovernanceRuleRecord, HookFallbackRecord, HookReceiptRecord, ContextReceiptRecord, ContextSelectionEventRecord, PolicyVerificationBaselineRecord, MemoryDistillCheckpointRecord, MemoryTurnRecord, MemoryTurnStatus, TelemetryFailureRecord, } from './database';
3
- export type { EnqueueWorkInput, LeaseWorkInput, RequestSessionClosureInput, SessionClosureRecord, SessionClosureState, WorkError, WorkItemRecord, WorkKind, WorkQueueHealth, WorkState, } from './work-queue';
3
+ export type { EnqueueWorkInput, LeaseWorkInput, RequestSessionClosureInput, SessionClosureRecord, SessionClosureState, WorkError, WorkItemRecord, WorkKind, WorkQueueHealth, WorkState, ProjectMutationLeaseRecord, } from './work-queue';
4
4
  export type { RetrievalLedgerEventRecord, RetrievalLedgerSourceType, RetrievalLedgerStage, } from './retrieval-ledger';
5
5
  export { hostActionReportsEqual, isHostActionState, mapHostActionRow, serializeHostActionReport, } from './host-actions';
6
6
  export { RETRIEVAL_MAX_CYCLES, isRetrievalSessionState } from './retrieval-sessions';
@@ -8,6 +8,8 @@ export type { AppendRetrievalCycleInput, CreateRetrievalSessionInput, RetrievalC
8
8
  export type { FailHostActionInput, HostActionRecord, HostActionState, ReportHostActionInput, RequestHostActionInput, StartHostActionInput, VerifyHostActionInput, } from './host-actions';
9
9
  export { mapSessionObligationRow, normalizeTurnId, } from './obligation-ledger';
10
10
  export { assertTaskIdentity, mapChannelQueryPlanRow, mapTaskIntentArtifactRow, mapTerminalTransitionRow, mapToolNameResolutionRow, mapTranscriptCheckpointRow, stableSemanticJson, } from './task-semantic-control';
11
+ export { mapTaskRuntimeEventRow, mapTaskRuntimeSnapshotRow, stableTaskRuntimeHash, stableTaskRuntimeJson, } from './task-runtime';
12
+ export type { TaskRuntimeChannelRecord, TaskRuntimeChannelRequirement, TaskRuntimeChannelStatus, TaskRuntimeEventRecord, TaskRuntimeIdentityRecord, TaskRuntimeObligationRecord, TaskRuntimeObligationState, TaskRuntimeSnapshotRecord, } from './task-runtime';
11
13
  export type { ChannelQueryPlanRecord, TaskIdentityRecord, TaskIntentArtifactRecord, TerminalTransitionRecord, ToolNameResolutionRecord, ToolNameResolutionStatus, TranscriptCheckpointRecord, } from './task-semantic-control';
12
14
  export { LEGAL_WORKFLOW_WORKER_TRANSITIONS, TERMINAL_WORKFLOW_WORKER_STATES, mapWorkflowMergeRow, mapWorkflowWorkerEventRow, mapWorkflowWorkerRow, } from './workflow-workers';
13
15
  export type { CreateWorkflowWorkerInput, EnqueueWorkflowMergeInput, TransitionWorkflowWorkerInput, WorkflowMergeRecord, WorkflowMergeState, WorkflowWorkerEventRecord, WorkflowWorkerRecord, WorkflowWorkerState, } from './workflow-workers';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stableLearningJson = exports.mapLearningVersionRow = exports.mapLearningEvidenceRow = exports.mapLearningCandidateRow = exports.mapWorkflowWorkerRow = exports.mapWorkflowWorkerEventRow = exports.mapWorkflowMergeRow = exports.TERMINAL_WORKFLOW_WORKER_STATES = exports.LEGAL_WORKFLOW_WORKER_TRANSITIONS = exports.stableSemanticJson = exports.mapTranscriptCheckpointRow = exports.mapToolNameResolutionRow = exports.mapTerminalTransitionRow = exports.mapTaskIntentArtifactRow = exports.mapChannelQueryPlanRow = exports.assertTaskIdentity = exports.normalizeTurnId = exports.mapSessionObligationRow = exports.isRetrievalSessionState = exports.RETRIEVAL_MAX_CYCLES = exports.serializeHostActionReport = exports.mapHostActionRow = exports.isHostActionState = exports.hostActionReportsEqual = exports.openGlobalDevFlowReadOnlyDatabase = exports.openGlobalDevFlowDatabase = exports.getGlobalDevFlowDbPath = exports.DevFlowDatabase = void 0;
3
+ exports.stableLearningJson = exports.mapLearningVersionRow = exports.mapLearningEvidenceRow = exports.mapLearningCandidateRow = exports.mapWorkflowWorkerRow = exports.mapWorkflowWorkerEventRow = exports.mapWorkflowMergeRow = exports.TERMINAL_WORKFLOW_WORKER_STATES = exports.LEGAL_WORKFLOW_WORKER_TRANSITIONS = exports.stableTaskRuntimeJson = exports.stableTaskRuntimeHash = exports.mapTaskRuntimeSnapshotRow = exports.mapTaskRuntimeEventRow = exports.stableSemanticJson = exports.mapTranscriptCheckpointRow = exports.mapToolNameResolutionRow = exports.mapTerminalTransitionRow = exports.mapTaskIntentArtifactRow = exports.mapChannelQueryPlanRow = exports.assertTaskIdentity = exports.normalizeTurnId = exports.mapSessionObligationRow = exports.isRetrievalSessionState = exports.RETRIEVAL_MAX_CYCLES = exports.serializeHostActionReport = exports.mapHostActionRow = exports.isHostActionState = exports.hostActionReportsEqual = exports.openGlobalDevFlowReadOnlyDatabase = exports.openGlobalDevFlowDatabase = exports.getGlobalDevFlowDbPath = exports.DevFlowDatabase = void 0;
4
4
  var database_1 = require("./database");
5
5
  Object.defineProperty(exports, "DevFlowDatabase", { enumerable: true, get: function () { return database_1.DevFlowDatabase; } });
6
6
  Object.defineProperty(exports, "getGlobalDevFlowDbPath", { enumerable: true, get: function () { return database_1.getGlobalDevFlowDbPath; } });
@@ -25,6 +25,11 @@ Object.defineProperty(exports, "mapTerminalTransitionRow", { enumerable: true, g
25
25
  Object.defineProperty(exports, "mapToolNameResolutionRow", { enumerable: true, get: function () { return task_semantic_control_1.mapToolNameResolutionRow; } });
26
26
  Object.defineProperty(exports, "mapTranscriptCheckpointRow", { enumerable: true, get: function () { return task_semantic_control_1.mapTranscriptCheckpointRow; } });
27
27
  Object.defineProperty(exports, "stableSemanticJson", { enumerable: true, get: function () { return task_semantic_control_1.stableSemanticJson; } });
28
+ var task_runtime_1 = require("./task-runtime");
29
+ Object.defineProperty(exports, "mapTaskRuntimeEventRow", { enumerable: true, get: function () { return task_runtime_1.mapTaskRuntimeEventRow; } });
30
+ Object.defineProperty(exports, "mapTaskRuntimeSnapshotRow", { enumerable: true, get: function () { return task_runtime_1.mapTaskRuntimeSnapshotRow; } });
31
+ Object.defineProperty(exports, "stableTaskRuntimeHash", { enumerable: true, get: function () { return task_runtime_1.stableTaskRuntimeHash; } });
32
+ Object.defineProperty(exports, "stableTaskRuntimeJson", { enumerable: true, get: function () { return task_runtime_1.stableTaskRuntimeJson; } });
28
33
  var workflow_workers_1 = require("./workflow-workers");
29
34
  Object.defineProperty(exports, "LEGAL_WORKFLOW_WORKER_TRANSITIONS", { enumerable: true, get: function () { return workflow_workers_1.LEGAL_WORKFLOW_WORKER_TRANSITIONS; } });
30
35
  Object.defineProperty(exports, "TERMINAL_WORKFLOW_WORKER_STATES", { enumerable: true, get: function () { return workflow_workers_1.TERMINAL_WORKFLOW_WORKER_STATES; } });
@@ -1,6 +1,7 @@
1
- export type RetrievalLedgerStage = 'eligible' | 'candidate' | 'selected' | 'exposed' | 'adopted' | 'positive_outcome' | 'negative_outcome' | 'unknown' | 'rejected';
1
+ export type RetrievalLedgerStage = 'eligible' | 'candidate' | 'selected' | 'exposed' | 'adopted' | 'consumed' | 'verified' | 'positive_outcome' | 'negative_outcome' | 'unknown' | 'unknown_outcome' | 'rejected';
2
2
  export type RetrievalLedgerSourceType = 'code' | 'memory' | 'knowledge' | 'action';
3
3
  export interface RetrievalLedgerEventRecord {
4
+ schemaVersion?: 'retrieval-ledger-event.v1';
4
5
  id: string;
5
6
  projectRoot: string;
6
7
  sessionId: string;
@@ -10,6 +11,12 @@ export interface RetrievalLedgerEventRecord {
10
11
  contextReceipt: string;
11
12
  sourceType: RetrievalLedgerSourceType;
12
13
  sourceId: string;
14
+ taskSpecHash?: string;
15
+ actor?: string;
16
+ sourceVersion?: string;
17
+ sourceContentHash?: string;
18
+ evidenceIds?: string[];
19
+ reasonCode?: string;
13
20
  stage: RetrievalLedgerStage;
14
21
  rank?: number;
15
22
  rawScore?: number;
@@ -0,0 +1,64 @@
1
+ export interface TaskRuntimeIdentityRecord {
2
+ projectRoot: string;
3
+ projectId: string;
4
+ hostId: string;
5
+ sessionId: string;
6
+ turnId: string;
7
+ requestId: string;
8
+ executionId?: string;
9
+ }
10
+ export interface TaskRuntimeEventRecord {
11
+ schemaVersion: 'task-runtime-event.v1';
12
+ producer: string;
13
+ producerVersion: string;
14
+ eventId: string;
15
+ sequence: number;
16
+ identity: TaskRuntimeIdentityRecord;
17
+ taskSpecHash: string;
18
+ kind: string;
19
+ payload: Record<string, unknown>;
20
+ createdAt: number;
21
+ }
22
+ export type TaskRuntimeChannelRequirement = 'required' | 'optional' | 'not_required';
23
+ export type TaskRuntimeChannelStatus = 'satisfied' | 'intentional_abstention' | 'empty_valid' | 'degraded' | 'failed';
24
+ export type TaskRuntimeObligationState = 'pending' | 'satisfied' | 'degraded' | 'failed';
25
+ export interface TaskRuntimeChannelRecord {
26
+ requirement: TaskRuntimeChannelRequirement;
27
+ status: TaskRuntimeChannelStatus;
28
+ reasonCode: string;
29
+ evidenceIds: string[];
30
+ material: boolean;
31
+ }
32
+ export interface TaskRuntimeObligationRecord {
33
+ requirement: TaskRuntimeChannelRequirement;
34
+ state: TaskRuntimeObligationState;
35
+ receiptId?: string;
36
+ reasonCode?: string;
37
+ }
38
+ export interface TaskRuntimeSnapshotRecord {
39
+ schemaVersion: 'task-runtime-snapshot.v1';
40
+ producer: string;
41
+ producerVersion: string;
42
+ identity: TaskRuntimeIdentityRecord;
43
+ taskSpecHash: string;
44
+ lastEventSequence: number;
45
+ artifacts: {
46
+ requiredIdentityIds: string[];
47
+ resolvedIdentityIds: string[];
48
+ ambiguousIdentityIds: string[];
49
+ };
50
+ channels: Partial<Record<'code' | 'memory' | 'knowledge', TaskRuntimeChannelRecord>>;
51
+ contextReceiptId?: string;
52
+ action: TaskRuntimeObligationRecord;
53
+ verification: TaskRuntimeObligationRecord;
54
+ memory: TaskRuntimeObligationRecord;
55
+ durableWorkIds: string[];
56
+ terminal: 'running' | 'retry_pending' | 'completed' | 'completed_with_degradation' | 'failed';
57
+ runtimeHealth: 'healthy' | 'degraded' | 'unavailable';
58
+ snapshotHash: string;
59
+ updatedAt: number;
60
+ }
61
+ export declare function stableTaskRuntimeJson(value: unknown): string;
62
+ export declare function stableTaskRuntimeHash(value: unknown): string;
63
+ export declare function mapTaskRuntimeEventRow(row: Record<string, unknown>): TaskRuntimeEventRecord;
64
+ export declare function mapTaskRuntimeSnapshotRow(row: Record<string, unknown>): TaskRuntimeSnapshotRecord;