mdkg 0.1.10 → 0.2.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/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@ This project follows a pragmatic changelog style inspired by Keep a Changelog. V
6
6
 
7
7
  mdkg is pre-v1 public alpha software. Command, graph, cache, bundle, and DAL contracts may change quickly while the project converges on a stable v1 surface.
8
8
 
9
+ ## 0.2.0 - Unreleased
10
+
11
+ Release numbering note: future project DB materializer/profile release planning
12
+ should follow `0.1.9 -> 0.2.0` rather than continuing the line as `0.1.10`
13
+ when the release represents a capability-track boundary.
14
+
15
+ ### Added
16
+
17
+ - Added public `mdkg db queue ...` commands for local project DB queue
18
+ create/pause/resume/enqueue/claim/ack/fail/dead-letter/release-expired/stats/list/show.
19
+ - Added `mdkg.project_db.queue_control.v1` / `005_mdkg_project_db_queue_control.sql`
20
+ for first-class queue active/paused state and migration backfill from existing
21
+ queue messages.
22
+ - Added queue-aware snapshot sealing policies: default `--queue-policy drain`
23
+ blocks ready/leased messages, while `--queue-policy paused` allows ready
24
+ messages only in paused queues and always blocks leased messages.
25
+ - Added packed CLI-only `smoke:db-queue-cli` coverage that exercises public
26
+ queue commands and pause/drain snapshot behavior from an installed tarball.
27
+
28
+ ### Changed
29
+
30
+ - Source release line now targets `0.2.0` for the next project DB
31
+ materializer/profile capability track.
32
+ - Project DB queue support is now public under `mdkg db queue`; event, reducer,
33
+ writer lease, and materializer command surfaces remain internal-only.
34
+
9
35
  ## 0.1.10 - 2026-06-05
10
36
 
11
37
  ### Added
package/README.md CHANGED
@@ -14,7 +14,7 @@ mdkg stays deliberately boring:
14
14
  - first-class rebuildable SQLite cache through built-in `node:sqlite`
15
15
  - no daemon, hosted index, or vector DB
16
16
 
17
- Current package version in source: `0.1.10`
17
+ Current package version in source: `0.2.0`
18
18
 
19
19
  mdkg is still pre-v1 public alpha software. The public package is usable, but graph, cache, bundle, and DAL contracts may continue to change quickly while the project converges on a stable v1 surface.
20
20
 
@@ -349,16 +349,16 @@ Runtime DB files, WAL, SHM, journal, lock, and temp files are ignored by
349
349
  default. `mdkg db init` does not create an active runtime SQLite database.
350
350
  Run `mdkg db migrate` after init to create or update the active runtime
351
351
  SQLite database at the configured `db.runtime_path`; built-in migrations write
352
- mdkg-owned generic foundation tables, then the internal local node:sqlite queue
353
- foundation, then internal local event/receipt/reducer and writer lease/CAS
354
- foundations, and record migration order, checksums, and applied timestamps.
355
- Queue state is durable local delivery infrastructure, not canonical event
356
- history. Event rows are the durable local history for project DB state
357
- transitions, receipts provide audit/review artifacts, reducers gate writes,
358
- writer leases coordinate snapshot-hash compare-and-swap commits, and
359
- materializers run local queue-backed reducer passes. These capabilities are
360
- available only through internal helper modules in this release; there is no
361
- public `mdkg db queue`, `mdkg db event`, `mdkg db reducer`, `mdkg db lease`, or
352
+ mdkg-owned generic foundation tables, public local node:sqlite queue delivery
353
+ tables, internal local event/receipt/reducer tables, writer lease/CAS tables,
354
+ and queue control state, then record migration order, checksums, and applied
355
+ timestamps. Queue state is durable local delivery infrastructure, not canonical
356
+ event history. Use `mdkg db queue create|pause|resume|enqueue|claim|ack|fail|dead-letter|release-expired|stats|list|show`
357
+ to operate local project queues. Paused queues reject enqueue/claim while still
358
+ allowing ack/fail/dead-letter/release-expired so leased work can settle. Event
359
+ rows are durable local project DB history; receipts, reducers, writer leases,
360
+ and materializers remain internal helper surfaces in this release, with no
361
+ public `mdkg db event`, `mdkg db reducer`, `mdkg db lease`, or
362
362
  `mdkg db materializer` CLI yet.
363
363
  Use `mdkg db verify` for non-mutating health checks over config, layout,
364
364
  runtime SQLite integrity, migration metadata, and transient runtime files. Use
@@ -366,6 +366,9 @@ runtime SQLite integrity, migration metadata, and transient runtime files. Use
366
366
  receipt-file count, and state snapshot presence.
367
367
  Use `mdkg db snapshot seal` to create an explicit sealed checkpoint at
368
368
  `.mdkg/db/state/project.sqlite` with `.mdkg/db/state/project.manifest.json`.
369
+ The default queue policy is `--queue-policy drain`, which requires no ready or
370
+ leased queue messages. Use `--queue-policy paused` only when ready messages are
371
+ intentionally preserved in paused queues; leased messages always block sealing.
369
372
  Use `mdkg db snapshot verify` and `mdkg db snapshot status` for checkpoint
370
373
  health, and use `mdkg db snapshot dump` / `mdkg db snapshot diff` as
371
374
  deterministic review aids for SQLite snapshots instead of comparing raw binary
package/dist/cli.js CHANGED
@@ -194,18 +194,41 @@ function printDbHelp(log, subcommand) {
194
194
  return;
195
195
  case "snapshot":
196
196
  log("Usage:");
197
- log(" mdkg db snapshot seal [--json]");
197
+ log(" mdkg db snapshot seal [--queue-policy drain|paused] [--json]");
198
198
  log(" mdkg db snapshot verify [--json]");
199
199
  log(" mdkg db snapshot status [--json]");
200
200
  log(" mdkg db snapshot dump [--snapshot <path>] [--output <path>] [--json]");
201
201
  log(" mdkg db snapshot diff <left-snapshot> <right-snapshot> [--json]");
202
202
  log("\nBoundaries:");
203
203
  log(" - snapshot seal writes a clean opt-in sealed project DB checkpoint");
204
+ log(" - default queue policy is drain: no ready or leased queue messages");
205
+ log(" - paused queue policy allows ready messages only in paused queues and never leased messages");
204
206
  log(" - snapshot verify/status read `.mdkg/db/state/project.sqlite` and its manifest");
205
207
  log(" - snapshot dump/diff are deterministic review aids, not source of truth");
206
208
  log(" - active runtime/WAL files remain ignored by default");
207
209
  printGlobalOptions(log);
208
210
  return;
211
+ case "queue":
212
+ log("Usage:");
213
+ log(" mdkg db queue create <queue> [--paused] [--reason <text>] [--json]");
214
+ log(" mdkg db queue pause <queue> [--reason <text>] [--json]");
215
+ log(" mdkg db queue resume <queue> [--json]");
216
+ log(" mdkg db queue enqueue <queue> <message-id> --payload-json <json>|--payload-file <path> [--dedupe-key <key>] [--available-at-ms <ms>] [--max-attempts <n>] [--json]");
217
+ log(" mdkg db queue claim <queue> --lease-owner <owner> --lease-ms <ms> [--json]");
218
+ log(" mdkg db queue ack <queue> <message-id> --lease-owner <owner> [--json]");
219
+ log(" mdkg db queue fail <queue> <message-id> --lease-owner <owner> --error <text> [--retry-after-ms <ms>] [--json]");
220
+ log(" mdkg db queue dead-letter <queue> <message-id> --lease-owner <owner> --error <text> [--json]");
221
+ log(" mdkg db queue release-expired [queue] [--json]");
222
+ log(" mdkg db queue stats [queue] [--json]");
223
+ log(" mdkg db queue list <queue> [--status ready|leased|acked|dead_letter|all] [--limit <n>] [--json]");
224
+ log(" mdkg db queue show <queue> <message-id> [--json]");
225
+ log("\nSemantics:");
226
+ log(" - queues are durable local delivery state, not canonical event history");
227
+ log(" - paused queues reject enqueue and claim");
228
+ log(" - ack, fail, dead-letter, and release-expired are allowed while paused so leased work can settle");
229
+ log(" - no raw SQL or hosted queue dependency is exposed");
230
+ printGlobalOptions(log);
231
+ return;
209
232
  default:
210
233
  log("Usage:");
211
234
  log(" mdkg db index rebuild [--tolerant] [--json]");
@@ -215,7 +238,13 @@ function printDbHelp(log, subcommand) {
215
238
  log(" mdkg db migrate [--json]");
216
239
  log(" mdkg db verify [--json]");
217
240
  log(" mdkg db stats [--json]");
218
- log(" mdkg db snapshot seal [--json]");
241
+ log(" mdkg db queue create <queue> [--paused] [--reason <text>] [--json]");
242
+ log(" mdkg db queue enqueue <queue> <message-id> --payload-json <json>|--payload-file <path> [--json]");
243
+ log(" mdkg db queue claim <queue> --lease-owner <owner> --lease-ms <ms> [--json]");
244
+ log(" mdkg db queue ack|fail|dead-letter <queue> <message-id> --lease-owner <owner> [--json]");
245
+ log(" mdkg db queue pause|resume <queue> [--json]");
246
+ log(" mdkg db queue stats|list|show ... [--json]");
247
+ log(" mdkg db snapshot seal [--queue-policy drain|paused] [--json]");
219
248
  log(" mdkg db snapshot verify [--json]");
220
249
  log(" mdkg db snapshot status [--json]");
221
250
  log(" mdkg db snapshot dump [--snapshot <path>] [--output <path>] [--json]");
@@ -227,14 +256,15 @@ function printDbHelp(log, subcommand) {
227
256
  log(" - `mdkg db init` does not create an active runtime SQLite database");
228
257
  log(" - `mdkg db migrate` creates/updates the active runtime SQLite database");
229
258
  log(" - `mdkg db migrate` applies mdkg-owned foundation plus internal queue, event, receipt, reducer, and lease migrations");
230
- log(" - queue tables are internal local delivery state; no public `mdkg db queue` CLI is exposed");
259
+ log(" - `mdkg db queue ...` exposes local durable queue delivery operations");
260
+ log(" - paused queues reject enqueue/claim and can be sealed with explicit paused snapshot policy");
231
261
  log(" - event rows are durable local history; receipts, reducers, and writer leases remain internal helper surfaces");
232
262
  log(" - no public `mdkg db event`, `mdkg db reducer`, or `mdkg db lease` CLI is exposed");
233
263
  log(" - `mdkg db verify` checks config, layout, SQLite integrity, migrations, and transient files");
234
264
  log(" - `mdkg db stats` reports table counts, migration state, DB size, and receipt counts");
235
265
  log(" - `mdkg db snapshot ...` manages opt-in sealed checkpoints and review dumps");
236
266
  log(" - active `.mdkg/db/runtime` and transient DB files are ignored by default");
237
- log(" - no raw SQL, hosted queue/event store, profile, public queue/event/reducer/lease command, or publish behavior is exposed here");
267
+ log(" - no raw SQL, hosted queue/event store, profile, public event/reducer/lease command, or publish behavior is exposed here");
238
268
  printGlobalOptions(log);
239
269
  }
240
270
  }
@@ -922,6 +952,16 @@ function parseNumberFlag(flag, value) {
922
952
  }
923
953
  return parsed;
924
954
  }
955
+ function parseQueuePolicyFlag(value) {
956
+ const raw = requireFlagValue("--queue-policy", value);
957
+ if (raw === undefined) {
958
+ return undefined;
959
+ }
960
+ if (raw === "drain" || raw === "paused") {
961
+ return raw;
962
+ }
963
+ throw new errors_1.UsageError("--queue-policy must be drain or paused");
964
+ }
925
965
  function parseEdgesFlag(value) {
926
966
  if (value === undefined) {
927
967
  return undefined;
@@ -1109,6 +1149,107 @@ function runDbSubcommand(parsed, root) {
1109
1149
  (0, db_1.runDbStatsCommand)({ root, json: parseBooleanFlag("--json", parsed.flags["--json"]) });
1110
1150
  return 0;
1111
1151
  }
1152
+ case "queue": {
1153
+ const action = (parsed.positionals[2] ?? "").toLowerCase();
1154
+ const json = parseBooleanFlag("--json", parsed.flags["--json"]);
1155
+ const queueName = parsed.positionals[3];
1156
+ const messageId = parsed.positionals[4];
1157
+ const common = {
1158
+ root,
1159
+ json,
1160
+ queueName,
1161
+ messageId,
1162
+ leaseOwner: requireFlagValue("--lease-owner", parsed.flags["--lease-owner"]),
1163
+ leaseMs: parseNumberFlag("--lease-ms", parsed.flags["--lease-ms"]),
1164
+ payloadJson: requireFlagValue("--payload-json", parsed.flags["--payload-json"]),
1165
+ payloadFile: requireFlagValue("--payload-file", parsed.flags["--payload-file"]),
1166
+ dedupeKey: requireFlagValue("--dedupe-key", parsed.flags["--dedupe-key"]),
1167
+ availableAtMs: parseNumberFlag("--available-at-ms", parsed.flags["--available-at-ms"]),
1168
+ maxAttempts: parseNumberFlag("--max-attempts", parsed.flags["--max-attempts"]),
1169
+ retryAfterMs: parseNumberFlag("--retry-after-ms", parsed.flags["--retry-after-ms"]),
1170
+ error: requireFlagValue("--error", parsed.flags["--error"]),
1171
+ paused: parseBooleanFlag("--paused", parsed.flags["--paused"]),
1172
+ reason: requireFlagValue("--reason", parsed.flags["--reason"]),
1173
+ status: requireFlagValue("--status", parsed.flags["--status"]),
1174
+ limit: parseNumberFlag("--limit", parsed.flags["--limit"]),
1175
+ };
1176
+ switch (action) {
1177
+ case "create":
1178
+ if (!queueName || parsed.positionals.length > 4) {
1179
+ throw new errors_1.UsageError("mdkg db queue create requires <queue>");
1180
+ }
1181
+ (0, db_1.runDbQueueCreateCommand)(common);
1182
+ return 0;
1183
+ case "pause":
1184
+ if (!queueName || parsed.positionals.length > 4) {
1185
+ throw new errors_1.UsageError("mdkg db queue pause requires <queue>");
1186
+ }
1187
+ (0, db_1.runDbQueuePauseCommand)(common);
1188
+ return 0;
1189
+ case "resume":
1190
+ if (!queueName || parsed.positionals.length > 4) {
1191
+ throw new errors_1.UsageError("mdkg db queue resume requires <queue>");
1192
+ }
1193
+ (0, db_1.runDbQueueResumeCommand)(common);
1194
+ return 0;
1195
+ case "enqueue":
1196
+ if (!queueName || !messageId || parsed.positionals.length > 5) {
1197
+ throw new errors_1.UsageError("mdkg db queue enqueue requires <queue> <message-id>");
1198
+ }
1199
+ (0, db_1.runDbQueueEnqueueCommand)(common);
1200
+ return 0;
1201
+ case "claim":
1202
+ if (!queueName || parsed.positionals.length > 4) {
1203
+ throw new errors_1.UsageError("mdkg db queue claim requires <queue>");
1204
+ }
1205
+ (0, db_1.runDbQueueClaimCommand)(common);
1206
+ return 0;
1207
+ case "ack":
1208
+ if (!queueName || !messageId || parsed.positionals.length > 5) {
1209
+ throw new errors_1.UsageError("mdkg db queue ack requires <queue> <message-id>");
1210
+ }
1211
+ (0, db_1.runDbQueueAckCommand)(common);
1212
+ return 0;
1213
+ case "fail":
1214
+ if (!queueName || !messageId || parsed.positionals.length > 5) {
1215
+ throw new errors_1.UsageError("mdkg db queue fail requires <queue> <message-id>");
1216
+ }
1217
+ (0, db_1.runDbQueueFailCommand)(common);
1218
+ return 0;
1219
+ case "dead-letter":
1220
+ if (!queueName || !messageId || parsed.positionals.length > 5) {
1221
+ throw new errors_1.UsageError("mdkg db queue dead-letter requires <queue> <message-id>");
1222
+ }
1223
+ (0, db_1.runDbQueueDeadLetterCommand)(common);
1224
+ return 0;
1225
+ case "release-expired":
1226
+ if (parsed.positionals.length > 4) {
1227
+ throw new errors_1.UsageError("mdkg db queue release-expired accepts at most one queue");
1228
+ }
1229
+ (0, db_1.runDbQueueReleaseExpiredCommand)(common);
1230
+ return 0;
1231
+ case "stats":
1232
+ if (parsed.positionals.length > 4) {
1233
+ throw new errors_1.UsageError("mdkg db queue stats accepts at most one queue");
1234
+ }
1235
+ (0, db_1.runDbQueueStatsCommand)(common);
1236
+ return 0;
1237
+ case "list":
1238
+ if (!queueName || parsed.positionals.length > 4) {
1239
+ throw new errors_1.UsageError("mdkg db queue list requires <queue>");
1240
+ }
1241
+ (0, db_1.runDbQueueListCommand)(common);
1242
+ return 0;
1243
+ case "show":
1244
+ if (!queueName || !messageId || parsed.positionals.length > 5) {
1245
+ throw new errors_1.UsageError("mdkg db queue show requires <queue> <message-id>");
1246
+ }
1247
+ (0, db_1.runDbQueueShowCommand)(common);
1248
+ return 0;
1249
+ default:
1250
+ throw new errors_1.UsageError("mdkg db queue requires create/pause/resume/enqueue/claim/ack/fail/dead-letter/release-expired/stats/list/show");
1251
+ }
1252
+ }
1112
1253
  case "snapshot": {
1113
1254
  const action = (parsed.positionals[2] ?? "").toLowerCase();
1114
1255
  const json = parseBooleanFlag("--json", parsed.flags["--json"]);
@@ -1117,7 +1258,7 @@ function runDbSubcommand(parsed, root) {
1117
1258
  if (parsed.positionals.length > 3) {
1118
1259
  throw new errors_1.UsageError("mdkg db snapshot seal does not accept positional arguments");
1119
1260
  }
1120
- (0, db_1.runDbSnapshotSealCommand)({ root, json });
1261
+ (0, db_1.runDbSnapshotSealCommand)({ root, json, queuePolicy: parseQueuePolicyFlag(parsed.flags["--queue-policy"]) });
1121
1262
  return 0;
1122
1263
  case "verify":
1123
1264
  if (parsed.positionals.length > 3) {
@@ -1154,7 +1295,7 @@ function runDbSubcommand(parsed, root) {
1154
1295
  }
1155
1296
  }
1156
1297
  default:
1157
- throw new errors_1.UsageError("mdkg db requires index/init/migrate/verify/stats/snapshot");
1298
+ throw new errors_1.UsageError("mdkg db requires index/init/migrate/verify/stats/queue/snapshot");
1158
1299
  }
1159
1300
  }
1160
1301
  function runCapabilitySubcommand(parsed, root) {
@@ -8,6 +8,18 @@ exports.runDbInitCommand = runDbInitCommand;
8
8
  exports.runDbMigrateCommand = runDbMigrateCommand;
9
9
  exports.runDbVerifyCommand = runDbVerifyCommand;
10
10
  exports.runDbStatsCommand = runDbStatsCommand;
11
+ exports.runDbQueueCreateCommand = runDbQueueCreateCommand;
12
+ exports.runDbQueuePauseCommand = runDbQueuePauseCommand;
13
+ exports.runDbQueueResumeCommand = runDbQueueResumeCommand;
14
+ exports.runDbQueueEnqueueCommand = runDbQueueEnqueueCommand;
15
+ exports.runDbQueueClaimCommand = runDbQueueClaimCommand;
16
+ exports.runDbQueueAckCommand = runDbQueueAckCommand;
17
+ exports.runDbQueueFailCommand = runDbQueueFailCommand;
18
+ exports.runDbQueueDeadLetterCommand = runDbQueueDeadLetterCommand;
19
+ exports.runDbQueueReleaseExpiredCommand = runDbQueueReleaseExpiredCommand;
20
+ exports.runDbQueueStatsCommand = runDbQueueStatsCommand;
21
+ exports.runDbQueueListCommand = runDbQueueListCommand;
22
+ exports.runDbQueueShowCommand = runDbQueueShowCommand;
11
23
  exports.runDbSnapshotSealCommand = runDbSnapshotSealCommand;
12
24
  exports.runDbSnapshotVerifyCommand = runDbSnapshotVerifyCommand;
13
25
  exports.runDbSnapshotStatusCommand = runDbSnapshotStatusCommand;
@@ -23,6 +35,7 @@ const paths_1 = require("../core/paths");
23
35
  const project_db_1 = require("../core/project_db");
24
36
  const project_db_migrations_1 = require("../core/project_db_migrations");
25
37
  const project_db_snapshot_1 = require("../core/project_db_snapshot");
38
+ const project_db_queue_1 = require("../core/project_db_queue");
26
39
  const version_1 = require("../core/version");
27
40
  const index_1 = require("./index");
28
41
  const capabilities_indexer_1 = require("../graph/capabilities_indexer");
@@ -449,6 +462,177 @@ function runDbStatsCommand(options) {
449
462
  }
450
463
  }
451
464
  }
465
+ function requireQueueName(options) {
466
+ if (!options.queueName) {
467
+ throw new errors_1.UsageError("queue name is required");
468
+ }
469
+ return options.queueName;
470
+ }
471
+ function requireMessageId(options) {
472
+ if (!options.messageId) {
473
+ throw new errors_1.UsageError("message id is required");
474
+ }
475
+ return options.messageId;
476
+ }
477
+ function requireLeaseOwner(options) {
478
+ if (!options.leaseOwner) {
479
+ throw new errors_1.UsageError("--lease-owner is required");
480
+ }
481
+ return options.leaseOwner;
482
+ }
483
+ function requireLeaseMs(options) {
484
+ if (options.leaseMs === undefined) {
485
+ throw new errors_1.UsageError("--lease-ms is required");
486
+ }
487
+ return options.leaseMs;
488
+ }
489
+ function loadQueueDatabasePath(root) {
490
+ const config = (0, config_1.loadConfig)(root);
491
+ const verification = (0, project_db_migrations_1.verifyProjectDb)(root, config);
492
+ if (!verification.ok) {
493
+ throw new errors_1.ValidationError(`db queue requires a valid project DB; run mdkg db verify`);
494
+ }
495
+ return (0, project_db_1.resolveConfiguredProjectDbLayout)(root, config.db).runtimeFile;
496
+ }
497
+ function parseQueuePayload(options) {
498
+ const hasPayloadJson = options.payloadJson !== undefined;
499
+ const hasPayloadFile = options.payloadFile !== undefined;
500
+ if (hasPayloadJson === hasPayloadFile) {
501
+ throw new errors_1.UsageError("mdkg db queue enqueue requires exactly one of --payload-json or --payload-file");
502
+ }
503
+ const raw = hasPayloadJson
504
+ ? options.payloadJson
505
+ : fs_1.default.readFileSync(path_1.default.resolve(options.root, String(options.payloadFile)), "utf8");
506
+ try {
507
+ return JSON.parse(String(raw));
508
+ }
509
+ catch (err) {
510
+ throw new errors_1.UsageError(`queue payload must be valid JSON: ${err instanceof Error ? err.message : String(err)}`);
511
+ }
512
+ }
513
+ function writeQueueJsonOrText(action, payload, json) {
514
+ if (json) {
515
+ console.log(JSON.stringify({ action, ok: true, ...payload }, null, 2));
516
+ return;
517
+ }
518
+ console.log(action);
519
+ for (const [key, value] of Object.entries(payload)) {
520
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) {
521
+ console.log(`${key}: ${value}`);
522
+ }
523
+ else {
524
+ console.log(`${key}: ${JSON.stringify(value)}`);
525
+ }
526
+ }
527
+ }
528
+ function runQueueMutation(options, fn, action) {
529
+ const config = (0, config_1.loadConfig)(options.root);
530
+ (0, lock_1.withMutationLock)(options.root, config.index.lock_timeout_ms, () => {
531
+ const databasePath = loadQueueDatabasePath(options.root);
532
+ writeQueueJsonOrText(action, fn(databasePath), options.json);
533
+ });
534
+ }
535
+ function runDbQueueCreateCommand(options) {
536
+ runQueueMutation(options, (databasePath) => (0, project_db_queue_1.createProjectQueue)(databasePath, {
537
+ queue_name: requireQueueName(options),
538
+ paused: options.paused,
539
+ reason: options.reason,
540
+ }), "db-queue-create");
541
+ }
542
+ function runDbQueuePauseCommand(options) {
543
+ runQueueMutation(options, (databasePath) => ({ queue: (0, project_db_queue_1.pauseProjectQueue)(databasePath, { queue_name: requireQueueName(options), reason: options.reason }) }), "db-queue-pause");
544
+ }
545
+ function runDbQueueResumeCommand(options) {
546
+ runQueueMutation(options, (databasePath) => ({ queue: (0, project_db_queue_1.resumeProjectQueue)(databasePath, { queue_name: requireQueueName(options) }) }), "db-queue-resume");
547
+ }
548
+ function runDbQueueEnqueueCommand(options) {
549
+ runQueueMutation(options, (databasePath) => (0, project_db_queue_1.enqueueProjectQueueMessage)(databasePath, {
550
+ queue_name: requireQueueName(options),
551
+ message_id: requireMessageId(options),
552
+ dedupe_key: options.dedupeKey,
553
+ payload: parseQueuePayload(options),
554
+ available_at_ms: options.availableAtMs,
555
+ max_attempts: options.maxAttempts,
556
+ }), "db-queue-enqueue");
557
+ }
558
+ function runDbQueueClaimCommand(options) {
559
+ runQueueMutation(options, (databasePath) => ({
560
+ message: (0, project_db_queue_1.claimProjectQueueMessage)(databasePath, {
561
+ queue_name: requireQueueName(options),
562
+ lease_owner: requireLeaseOwner(options),
563
+ lease_ms: requireLeaseMs(options),
564
+ }),
565
+ }), "db-queue-claim");
566
+ }
567
+ function runDbQueueAckCommand(options) {
568
+ runQueueMutation(options, (databasePath) => ({
569
+ message: (0, project_db_queue_1.ackProjectQueueMessage)(databasePath, {
570
+ queue_name: requireQueueName(options),
571
+ message_id: requireMessageId(options),
572
+ lease_owner: requireLeaseOwner(options),
573
+ }),
574
+ }), "db-queue-ack");
575
+ }
576
+ function runDbQueueFailCommand(options) {
577
+ if (!options.error) {
578
+ throw new errors_1.UsageError("--error is required");
579
+ }
580
+ runQueueMutation(options, (databasePath) => ({
581
+ message: (0, project_db_queue_1.failProjectQueueMessage)(databasePath, {
582
+ queue_name: requireQueueName(options),
583
+ message_id: requireMessageId(options),
584
+ lease_owner: requireLeaseOwner(options),
585
+ error: String(options.error),
586
+ retry_after_ms: options.retryAfterMs,
587
+ }),
588
+ }), "db-queue-fail");
589
+ }
590
+ function runDbQueueDeadLetterCommand(options) {
591
+ if (!options.error) {
592
+ throw new errors_1.UsageError("--error is required");
593
+ }
594
+ runQueueMutation(options, (databasePath) => ({
595
+ message: (0, project_db_queue_1.deadLetterProjectQueueMessage)(databasePath, {
596
+ queue_name: requireQueueName(options),
597
+ message_id: requireMessageId(options),
598
+ lease_owner: requireLeaseOwner(options),
599
+ error: String(options.error),
600
+ }),
601
+ }), "db-queue-dead-letter");
602
+ }
603
+ function runDbQueueReleaseExpiredCommand(options) {
604
+ runQueueMutation(options, (databasePath) => (0, project_db_queue_1.releaseExpiredProjectQueueLeases)(databasePath, { queue_name: options.queueName }), "db-queue-release-expired");
605
+ }
606
+ function runDbQueueStatsCommand(options) {
607
+ const databasePath = loadQueueDatabasePath(options.root);
608
+ const stats = (0, project_db_queue_1.readProjectQueueStats)(databasePath, { queue_name: options.queueName });
609
+ const queue = options.queueName ? (0, project_db_queue_1.readProjectQueue)(databasePath, options.queueName) : null;
610
+ writeQueueJsonOrText("db-queue-stats", {
611
+ queue,
612
+ stats,
613
+ queues: options.queueName ? undefined : (0, project_db_queue_1.listProjectQueues)(databasePath),
614
+ snapshot_summary: (0, project_db_queue_1.readProjectQueueSnapshotSummary)(databasePath),
615
+ }, options.json);
616
+ }
617
+ function runDbQueueListCommand(options) {
618
+ const databasePath = loadQueueDatabasePath(options.root);
619
+ const messages = (0, project_db_queue_1.listProjectQueueMessages)(databasePath, {
620
+ queue_name: requireQueueName(options),
621
+ status: (options.status ?? "all"),
622
+ limit: options.limit,
623
+ });
624
+ writeQueueJsonOrText("db-queue-list", { queue_name: requireQueueName(options), count: messages.length, messages }, options.json);
625
+ }
626
+ function runDbQueueShowCommand(options) {
627
+ const databasePath = loadQueueDatabasePath(options.root);
628
+ const queueName = requireQueueName(options);
629
+ const messageId = requireMessageId(options);
630
+ const message = (0, project_db_queue_1.readProjectQueueMessage)(databasePath, queueName, messageId);
631
+ if (!message) {
632
+ throw new errors_1.NotFoundError(`queue message not found: ${queueName}/${messageId}`);
633
+ }
634
+ writeQueueJsonOrText("db-queue-show", { message }, options.json);
635
+ }
452
636
  function printSnapshotChecks(payload) {
453
637
  for (const check of payload.checks) {
454
638
  const location = check.path ? ` (${path_1.default.isAbsolute(check.path) ? rel(process.cwd(), check.path) : check.path})` : "";
@@ -457,7 +641,7 @@ function printSnapshotChecks(payload) {
457
641
  }
458
642
  function runDbSnapshotSealCommandLocked(options) {
459
643
  const config = (0, config_1.loadConfig)(options.root);
460
- const payload = (0, project_db_snapshot_1.sealProjectDbSnapshot)(options.root, config);
644
+ const payload = (0, project_db_snapshot_1.sealProjectDbSnapshot)(options.root, config, options.queuePolicy ?? "drain");
461
645
  if (options.json) {
462
646
  console.log(JSON.stringify(payload, null, 2));
463
647
  return;
@@ -53,6 +53,24 @@ ON project_queue_message(queue_name, status, available_at_ms, created_at_ms, mes
53
53
  CREATE INDEX IF NOT EXISTS project_queue_message_lease_idx
54
54
  ON project_queue_message(queue_name, status, lease_deadline_ms, created_at_ms, message_id);
55
55
  `;
56
+ const QUEUE_CONTROL_MIGRATION_SQL = `
57
+ CREATE TABLE IF NOT EXISTS project_queue (
58
+ queue_name TEXT PRIMARY KEY,
59
+ status TEXT NOT NULL CHECK(status IN ('active', 'paused')),
60
+ paused_reason TEXT,
61
+ created_at_ms INTEGER NOT NULL,
62
+ updated_at_ms INTEGER NOT NULL
63
+ ) STRICT;
64
+
65
+ INSERT OR IGNORE INTO project_queue (queue_name, status, paused_reason, created_at_ms, updated_at_ms)
66
+ SELECT DISTINCT
67
+ queue_name,
68
+ 'active',
69
+ NULL,
70
+ CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER),
71
+ CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)
72
+ FROM project_queue_message;
73
+ `;
56
74
  const EVENTS_RECEIPTS_MIGRATION_SQL = `
57
75
  CREATE TABLE IF NOT EXISTS project_event (
58
76
  event_id TEXT PRIMARY KEY,
@@ -162,6 +180,12 @@ const BUILTIN_MIGRATIONS = [
162
180
  filename: "004_mdkg_project_db_writer_leases.sql",
163
181
  sql: WRITER_LEASES_MIGRATION_SQL.trim(),
164
182
  },
183
+ {
184
+ ordinal: 5,
185
+ key: "mdkg.project_db.queue_control.v1",
186
+ filename: "005_mdkg_project_db_queue_control.sql",
187
+ sql: QUEUE_CONTROL_MIGRATION_SQL.trim(),
188
+ },
165
189
  ];
166
190
  function loadDatabaseCtor() {
167
191
  try {