mdkg 0.2.0 → 0.3.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.
@@ -6,11 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runWorkContractNewCommand = runWorkContractNewCommand;
7
7
  exports.runWorkOrderNewCommand = runWorkOrderNewCommand;
8
8
  exports.runWorkOrderUpdateCommand = runWorkOrderUpdateCommand;
9
+ exports.runWorkOrderStatusCommand = runWorkOrderStatusCommand;
10
+ exports.runWorkTriggerCommand = runWorkTriggerCommand;
9
11
  exports.runWorkReceiptNewCommand = runWorkReceiptNewCommand;
10
12
  exports.runWorkReceiptUpdateCommand = runWorkReceiptUpdateCommand;
13
+ exports.runWorkReceiptVerifyCommand = runWorkReceiptVerifyCommand;
11
14
  exports.runWorkArtifactAddCommand = runWorkArtifactAddCommand;
12
15
  const fs_1 = __importDefault(require("fs"));
13
16
  const path_1 = __importDefault(require("path"));
17
+ const crypto_1 = __importDefault(require("crypto"));
14
18
  const config_1 = require("../core/config");
15
19
  const frontmatter_1 = require("../graph/frontmatter");
16
20
  const indexer_1 = require("../graph/indexer");
@@ -21,15 +25,20 @@ const loader_1 = require("../templates/loader");
21
25
  const date_1 = require("../util/date");
22
26
  const errors_1 = require("../util/errors");
23
27
  const id_1 = require("../util/id");
28
+ const refs_1 = require("../util/refs");
24
29
  const qid_1 = require("../util/qid");
25
30
  const atomic_1 = require("../util/atomic");
26
31
  const lock_1 = require("../util/lock");
27
32
  const event_support_1 = require("./event_support");
28
33
  const archive_1 = require("./archive");
34
+ const project_db_1 = require("../core/project_db");
35
+ const project_db_migrations_1 = require("../core/project_db_migrations");
36
+ const project_db_queue_1 = require("../core/project_db_queue");
29
37
  const PRICING_MODELS = new Set(["free", "included", "quoted", "fixed", "metered", "subscription"]);
30
38
  const ORDER_STATUSES = new Set(["submitted", "accepted", "running", "completed", "cancelled", "failed"]);
31
39
  const RECEIPT_STATUSES = new Set(["recorded", "verified", "rejected", "superseded"]);
32
40
  const OUTCOMES = new Set(["success", "partial", "failure"]);
41
+ const REDACTION_POLICIES = new Set(["refs_and_hashes_only", "redacted_summary", "external_private"]);
33
42
  function parseCsvList(raw) {
34
43
  if (!raw) {
35
44
  return [];
@@ -70,6 +79,83 @@ function normalizeEnum(value, flag, allowed) {
70
79
  }
71
80
  return normalized;
72
81
  }
82
+ function normalizeSha256Ref(value, flag) {
83
+ if (value === undefined) {
84
+ return undefined;
85
+ }
86
+ const normalized = value.toLowerCase();
87
+ if (!(0, refs_1.isSha256Ref)(normalized)) {
88
+ throw new errors_1.UsageError(`${flag} must be sha256:<64 lowercase hex chars>`);
89
+ }
90
+ return normalized;
91
+ }
92
+ function normalizeSha256Refs(value, flag) {
93
+ return parseCsvList(value).map((hash) => normalizeSha256Ref(hash, flag));
94
+ }
95
+ function stableJson(value) {
96
+ if (Array.isArray(value)) {
97
+ return `[${value.map((item) => stableJson(item)).join(",")}]`;
98
+ }
99
+ if (value && typeof value === "object") {
100
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
101
+ return `{${entries
102
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
103
+ .join(",")}}`;
104
+ }
105
+ return JSON.stringify(value);
106
+ }
107
+ function hashStablePayload(value) {
108
+ return `sha256:${crypto_1.default.createHash("sha256").update(stableJson(value)).digest("hex")}`;
109
+ }
110
+ function buildWorkOrderPayloadHash(options) {
111
+ return hashStablePayload({
112
+ work_id: options.workId,
113
+ work_version: options.workVersion,
114
+ requester: options.requester,
115
+ request_ref: options.requestRef,
116
+ trigger_ref: options.triggerRef,
117
+ input_refs: options.inputRefs,
118
+ queue_refs: options.queueRefs,
119
+ requested_outputs: options.requestedOutputs,
120
+ constraint_refs: options.constraintRefs,
121
+ });
122
+ }
123
+ function portableSegment(value) {
124
+ return (value
125
+ .toLowerCase()
126
+ .replace(/[^a-z0-9_]+/g, ".")
127
+ .replace(/^[._-]+|[._-]+$/g, "")
128
+ .slice(0, 80) || "work");
129
+ }
130
+ function normalizeQueueName(value) {
131
+ if (value === undefined) {
132
+ return undefined;
133
+ }
134
+ const normalized = value.toLowerCase();
135
+ if (!(0, id_1.isPortableId)(normalized)) {
136
+ throw new errors_1.UsageError("--enqueue must be a lowercase portable queue name");
137
+ }
138
+ return normalized;
139
+ }
140
+ function queueRefForWorkOrder(queueName, orderId) {
141
+ return `queue://project-db/${queueName}/${orderId}`;
142
+ }
143
+ function loadWorkTriggerQueueDatabase(root, queueName) {
144
+ const config = (0, config_1.loadConfig)(root);
145
+ const verification = (0, project_db_migrations_1.verifyProjectDb)(root, config);
146
+ if (!verification.ok) {
147
+ throw new errors_1.ValidationError("work trigger --enqueue requires a valid project DB; run mdkg db init, mdkg db migrate, and mdkg db verify");
148
+ }
149
+ const databasePath = (0, project_db_1.resolveConfiguredProjectDbLayout)(root, config.db).runtimeFile;
150
+ const queue = (0, project_db_queue_1.readProjectQueue)(databasePath, queueName);
151
+ if (!queue) {
152
+ throw new errors_1.NotFoundError(`project DB queue not found: ${queueName}; run mdkg db queue create ${queueName}`);
153
+ }
154
+ if (queue.status !== "active") {
155
+ throw new errors_1.ValidationError(`project DB queue ${queueName} is paused; run mdkg db queue resume ${queueName}`);
156
+ }
157
+ return databasePath;
158
+ }
73
159
  function slugifyTitle(title) {
74
160
  const slug = title
75
161
  .trim()
@@ -113,6 +199,67 @@ function requireReferenceNode(index, ws, idOrQid, type, label) {
113
199
  }
114
200
  return node;
115
201
  }
202
+ function resolveReadableWorkNode(index, idOrQid, ws, type, label) {
203
+ const resolved = (0, qid_1.resolveQid)(index, idOrQid, ws);
204
+ if (resolved.status !== "ok") {
205
+ throw new errors_1.NotFoundError((0, qid_1.formatResolveError)(label, idOrQid, resolved, ws));
206
+ }
207
+ const node = index.nodes[resolved.qid];
208
+ if (!node || node.type !== type) {
209
+ throw new errors_1.NotFoundError(`${label} not found: ${idOrQid}`);
210
+ }
211
+ return node;
212
+ }
213
+ function resolveTriggerWorkNode(index, ws, refRaw) {
214
+ const ref = normalizePortableIdRef(refRaw, "<work-or-capability-ref>");
215
+ const resolved = (0, qid_1.resolveQid)(index, ref, ws);
216
+ if (resolved.status !== "ok") {
217
+ throw new errors_1.NotFoundError((0, qid_1.formatResolveError)("work contract or capability", refRaw, resolved, ws));
218
+ }
219
+ const node = index.nodes[resolved.qid];
220
+ if (!node) {
221
+ throw new errors_1.NotFoundError(`work contract or capability not found: ${refRaw}`);
222
+ }
223
+ if (node.type === "work") {
224
+ return { workNode: node };
225
+ }
226
+ if (node.type !== "spec") {
227
+ throw new errors_1.UsageError(`work trigger requires a WORK.md or SPEC.md ref, got ${node.type}: ${node.qid}`);
228
+ }
229
+ const candidates = new Map();
230
+ const specDir = path_1.default.posix.dirname(node.path);
231
+ for (const contractPath of toStringList(node.attributes.work_contracts)) {
232
+ const normalizedPath = path_1.default.posix.normalize(path_1.default.posix.join(specDir, contractPath));
233
+ for (const candidate of Object.values(index.nodes)) {
234
+ if (candidate.type === "work" && candidate.ws === node.ws && candidate.path === normalizedPath) {
235
+ candidates.set(candidate.qid, candidate);
236
+ }
237
+ }
238
+ }
239
+ for (const qid of node.edges.relates) {
240
+ const candidate = index.nodes[qid];
241
+ if (candidate?.type === "work") {
242
+ candidates.set(candidate.qid, candidate);
243
+ }
244
+ }
245
+ const reverseRelates = index.reverse_edges[node.qid]?.relates ?? [];
246
+ for (const qid of reverseRelates) {
247
+ const candidate = index.nodes[qid];
248
+ if (candidate?.type === "work") {
249
+ candidates.set(candidate.qid, candidate);
250
+ }
251
+ }
252
+ const workNodes = Array.from(candidates.values()).sort((a, b) => a.qid.localeCompare(b.qid));
253
+ if (workNodes.length === 0) {
254
+ throw new errors_1.NotFoundError(`SPEC.md ${node.qid} has no resolvable WORK.md contract`);
255
+ }
256
+ if (workNodes.length > 1) {
257
+ throw new errors_1.UsageError(`SPEC.md ${node.qid} has multiple work contracts; trigger one explicitly: ${workNodes
258
+ .map((workNode) => workNode.qid)
259
+ .join(", ")}`);
260
+ }
261
+ return { workNode: workNodes[0], sourceNode: node };
262
+ }
116
263
  function nodeReceipt(root, node) {
117
264
  return {
118
265
  workspace: node.ws,
@@ -123,6 +270,190 @@ function nodeReceipt(root, node) {
123
270
  title: node.title,
124
271
  };
125
272
  }
273
+ function resolveOptionalQid(index, ws, idOrQid) {
274
+ if (!idOrQid) {
275
+ return undefined;
276
+ }
277
+ const resolved = (0, qid_1.resolveQid)(index, idOrQid, ws);
278
+ return resolved.status === "ok" ? resolved.qid : undefined;
279
+ }
280
+ function listReceiptsForOrder(index, order) {
281
+ const orderRefs = new Set([order.id, order.qid, `${order.ws}:${order.id}`]);
282
+ return Object.values(index.nodes)
283
+ .filter((node) => node.type === "receipt" && orderRefs.has(String(node.attributes.work_order_id ?? "")))
284
+ .sort((a, b) => a.qid.localeCompare(b.qid));
285
+ }
286
+ function buildWorkOrderStatusReceipt(index, order) {
287
+ const workId = typeof order.attributes.work_id === "string" ? order.attributes.work_id : undefined;
288
+ const receipts = listReceiptsForOrder(index, order).map((receipt) => ({
289
+ id: receipt.id,
290
+ qid: receipt.qid,
291
+ path: receipt.path,
292
+ title: receipt.title,
293
+ receipt_status: typeof receipt.attributes.receipt_status === "string" ? receipt.attributes.receipt_status : undefined,
294
+ outcome: typeof receipt.attributes.outcome === "string" ? receipt.attributes.outcome : undefined,
295
+ redaction_policy: typeof receipt.attributes.redaction_policy === "string" ? receipt.attributes.redaction_policy : undefined,
296
+ artifacts: receipt.artifacts,
297
+ proof_refs: toStringList(receipt.attributes.proof_refs),
298
+ attestation_refs: toStringList(receipt.attributes.attestation_refs),
299
+ evidence_hashes: toStringList(receipt.attributes.evidence_hashes),
300
+ input_hashes: toStringList(receipt.attributes.input_hashes),
301
+ output_hashes: toStringList(receipt.attributes.output_hashes),
302
+ updated: receipt.updated,
303
+ }));
304
+ return {
305
+ kind: "work_order_status",
306
+ order: {
307
+ workspace: order.ws,
308
+ id: order.id,
309
+ qid: order.qid,
310
+ path: order.path,
311
+ title: order.title,
312
+ status: typeof order.attributes.order_status === "string" ? order.attributes.order_status : undefined,
313
+ work_id: workId,
314
+ work_qid: resolveOptionalQid(index, order.ws, workId),
315
+ requester: typeof order.attributes.requester === "string" ? order.attributes.requester : undefined,
316
+ request_ref: typeof order.attributes.request_ref === "string" ? order.attributes.request_ref : undefined,
317
+ trigger_ref: typeof order.attributes.trigger_ref === "string" ? order.attributes.trigger_ref : undefined,
318
+ payload_hash: typeof order.attributes.payload_hash === "string" ? order.attributes.payload_hash : undefined,
319
+ input_refs: toStringList(order.attributes.input_refs),
320
+ queue_refs: toStringList(order.attributes.queue_refs),
321
+ requested_outputs: toStringList(order.attributes.requested_outputs),
322
+ constraint_refs: toStringList(order.attributes.constraint_refs),
323
+ artifact_policy: typeof order.attributes.artifact_policy === "string" ? order.attributes.artifact_policy : undefined,
324
+ artifacts: order.artifacts,
325
+ created: order.created,
326
+ updated: order.updated,
327
+ },
328
+ receipt_count: receipts.length,
329
+ receipts,
330
+ };
331
+ }
332
+ function buildArchiveIdsByWorkspace(index) {
333
+ const byWorkspace = {};
334
+ for (const node of Object.values(index.nodes)) {
335
+ if (node.type !== "archive") {
336
+ continue;
337
+ }
338
+ if (!byWorkspace[node.ws]) {
339
+ byWorkspace[node.ws] = new Set();
340
+ }
341
+ byWorkspace[node.ws].add(node.id);
342
+ }
343
+ return byWorkspace;
344
+ }
345
+ function verifyArchiveRefs(index, ws, refsByField, errors) {
346
+ const archiveIdsByWorkspace = buildArchiveIdsByWorkspace(index);
347
+ for (const [field, refs] of Object.entries(refsByField)) {
348
+ for (const [indexValue, ref] of refs.entries()) {
349
+ if (!ref.startsWith("archive://")) {
350
+ continue;
351
+ }
352
+ const archiveId = (0, refs_1.archiveIdFromUri)(ref);
353
+ if (!archiveId) {
354
+ errors.push(`${field}[${indexValue}] has malformed archive ref ${ref}`);
355
+ continue;
356
+ }
357
+ if (!archiveIdsByWorkspace[ws]?.has(archiveId)) {
358
+ errors.push(`${field}[${indexValue}] references missing archive ${ref}`);
359
+ }
360
+ }
361
+ }
362
+ }
363
+ function addVerifyCheck(checks, errors, name, ok, detail) {
364
+ checks.push({ name, ok, detail });
365
+ if (!ok) {
366
+ errors.push(detail);
367
+ }
368
+ }
369
+ function buildWorkReceiptVerifyReceipt(index, receipt) {
370
+ const errors = [];
371
+ const warnings = [];
372
+ const checks = [];
373
+ const workOrderId = typeof receipt.attributes.work_order_id === "string" ? receipt.attributes.work_order_id : undefined;
374
+ const workOrder = workOrderId
375
+ ? resolveTypedReadableNode(index, receipt.ws, workOrderId, "work_order")
376
+ : undefined;
377
+ const workId = typeof workOrder?.attributes.work_id === "string" ? workOrder.attributes.work_id : undefined;
378
+ const workNode = workId ? resolveTypedReadableNode(index, workOrder?.ws ?? receipt.ws, workId, "work") : undefined;
379
+ const artifacts = receipt.artifacts;
380
+ const proofRefs = toStringList(receipt.attributes.proof_refs);
381
+ const attestationRefs = toStringList(receipt.attributes.attestation_refs);
382
+ const evidenceHashes = toStringList(receipt.attributes.evidence_hashes);
383
+ const inputHashes = toStringList(receipt.attributes.input_hashes);
384
+ const outputHashes = toStringList(receipt.attributes.output_hashes);
385
+ const evidenceCount = artifacts.length +
386
+ proofRefs.length +
387
+ attestationRefs.length +
388
+ evidenceHashes.length +
389
+ inputHashes.length +
390
+ outputHashes.length;
391
+ addVerifyCheck(checks, errors, "work_order_link", workOrder !== undefined, workOrder ? `linked to ${workOrder.qid}` : `work_order_id references missing WORK_ORDER.md ${workOrderId ?? ""}`.trim());
392
+ addVerifyCheck(checks, errors, "work_link", !workOrder || workNode !== undefined, workNode ? `linked to ${workNode.qid}` : workOrder ? `work_id references missing WORK.md ${workId ?? ""}`.trim() : "work order missing");
393
+ addVerifyCheck(checks, errors, "outcome", typeof receipt.attributes.outcome === "string", typeof receipt.attributes.outcome === "string" ? `outcome ${receipt.attributes.outcome}` : "outcome is missing");
394
+ addVerifyCheck(checks, errors, "receipt_status", receipt.attributes.receipt_status !== "rejected", receipt.attributes.receipt_status === "rejected"
395
+ ? "receipt_status is rejected"
396
+ : `receipt_status ${String(receipt.attributes.receipt_status ?? "unknown")}`);
397
+ addVerifyCheck(checks, errors, "evidence_present", evidenceCount > 0, evidenceCount > 0 ? `${evidenceCount} evidence reference(s) present` : "receipt has no artifacts, proof refs, attestations, or hashes");
398
+ const archiveErrors = [];
399
+ verifyArchiveRefs(index, receipt.ws, {
400
+ artifacts,
401
+ proof_refs: proofRefs,
402
+ attestation_refs: attestationRefs,
403
+ }, archiveErrors);
404
+ addVerifyCheck(checks, errors, "archive_refs", archiveErrors.length === 0, archiveErrors.length === 0 ? "archive refs resolve" : archiveErrors.join("; "));
405
+ if (typeof receipt.attributes.redaction_policy !== "string") {
406
+ warnings.push("redaction_policy is missing; legacy receipt remains readable but not explicitly redaction-scoped");
407
+ }
408
+ else {
409
+ addVerifyCheck(checks, errors, "redaction_policy", true, `redaction_policy ${receipt.attributes.redaction_policy}`);
410
+ }
411
+ return {
412
+ kind: "work_receipt_verify",
413
+ ok: errors.length === 0,
414
+ receipt: {
415
+ workspace: receipt.ws,
416
+ id: receipt.id,
417
+ qid: receipt.qid,
418
+ path: receipt.path,
419
+ title: receipt.title,
420
+ receipt_status: typeof receipt.attributes.receipt_status === "string" ? receipt.attributes.receipt_status : undefined,
421
+ outcome: typeof receipt.attributes.outcome === "string" ? receipt.attributes.outcome : undefined,
422
+ work_order_id: workOrderId,
423
+ work_order_qid: workOrder?.qid,
424
+ redaction_policy: typeof receipt.attributes.redaction_policy === "string" ? receipt.attributes.redaction_policy : undefined,
425
+ artifacts,
426
+ proof_refs: proofRefs,
427
+ attestation_refs: attestationRefs,
428
+ evidence_hashes: evidenceHashes,
429
+ input_hashes: inputHashes,
430
+ output_hashes: outputHashes,
431
+ updated: receipt.updated,
432
+ },
433
+ work_order: workOrder
434
+ ? {
435
+ id: workOrder.id,
436
+ qid: workOrder.qid,
437
+ path: workOrder.path,
438
+ status: typeof workOrder.attributes.order_status === "string" ? workOrder.attributes.order_status : undefined,
439
+ work_id: workId,
440
+ work_qid: workNode?.qid,
441
+ payload_hash: typeof workOrder.attributes.payload_hash === "string" ? workOrder.attributes.payload_hash : undefined,
442
+ }
443
+ : undefined,
444
+ checks,
445
+ errors,
446
+ warnings,
447
+ };
448
+ }
449
+ function resolveTypedReadableNode(index, ws, idOrQid, type) {
450
+ const resolved = (0, qid_1.resolveQid)(index, idOrQid, ws);
451
+ if (resolved.status !== "ok") {
452
+ return undefined;
453
+ }
454
+ const node = index.nodes[resolved.qid];
455
+ return node?.type === type ? node : undefined;
456
+ }
126
457
  function writeFrontmatterFile(filePath, frontmatter, body) {
127
458
  const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter, frontmatter_1.DEFAULT_FRONTMATTER_KEY_ORDER);
128
459
  const content = ["---", ...lines, "---", body.trimStart()].join("\n");
@@ -211,13 +542,62 @@ function printReceipt(action, receipt, json) {
211
542
  }
212
543
  console.log(`work ${action}: ${receipt.qid} (${receipt.path})`);
213
544
  }
545
+ function createWorkOrderForWork(options) {
546
+ const workVersion = String(options.workNode.attributes.version ?? "0.1.0");
547
+ const requestRef = options.requestRef ?? "request.redacted";
548
+ const triggerRef = options.triggerRef ?? "trigger.manual";
549
+ const inputRefs = parseCsvList(options.inputRefs);
550
+ const queueRefs = parseCsvList(options.queueRefs);
551
+ const requestedOutputs = options.requestedOutputs !== undefined
552
+ ? parseCsvList(options.requestedOutputs)
553
+ : toStringList(options.workNode.attributes.outputs);
554
+ const constraintRefs = parseCsvList(options.constraintRefs);
555
+ const payloadHash = normalizeSha256Ref(options.payloadHash, "--payload-hash") ??
556
+ buildWorkOrderPayloadHash({
557
+ workId: options.workId,
558
+ workVersion,
559
+ requester: options.requester,
560
+ requestRef,
561
+ triggerRef,
562
+ inputRefs,
563
+ queueRefs,
564
+ requestedOutputs,
565
+ constraintRefs,
566
+ });
567
+ const receipt = createAgentWorkflowNode({
568
+ root: options.root,
569
+ ws: options.ws,
570
+ type: "work_order",
571
+ title: options.title,
572
+ id: options.id,
573
+ now: options.now,
574
+ overrides: {
575
+ work_id: options.workId,
576
+ work_version: workVersion,
577
+ requester: options.requester,
578
+ order_status: "submitted",
579
+ request_ref: requestRef,
580
+ trigger_ref: triggerRef,
581
+ payload_hash: payloadHash,
582
+ input_refs: inputRefs,
583
+ queue_refs: queueRefs,
584
+ requested_outputs: requestedOutputs,
585
+ constraint_refs: constraintRefs,
586
+ artifact_policy: "commit_sidecar_and_zip",
587
+ relates: [options.workId],
588
+ },
589
+ });
590
+ return { receipt, payloadHash, workId: options.workId, workVersion };
591
+ }
214
592
  function runWorkContractNewCommandLocked(options) {
215
593
  const config = (0, config_1.loadConfig)(options.root);
216
594
  const ws = normalizeWorkspace(options.ws);
217
595
  const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
218
596
  const agentId = normalizePortableIdRef(options.agentId, "--agent-id");
597
+ const kind = options.kind.toLowerCase();
219
598
  const resolvedAgent = (0, qid_1.resolveQid)(index, agentId, ws);
220
599
  const relates = resolvedAgent.status === "ok" && index.nodes[resolvedAgent.qid]?.type === "spec" ? [agentId] : [];
600
+ const requiredCapabilities = parseCsvList(options.requiredCapabilities);
221
601
  const receipt = createAgentWorkflowNode({
222
602
  root: options.root,
223
603
  ws,
@@ -227,9 +607,9 @@ function runWorkContractNewCommandLocked(options) {
227
607
  now: options.now,
228
608
  overrides: {
229
609
  agent_id: agentId,
230
- kind: options.kind.toLowerCase(),
610
+ kind,
231
611
  pricing_model: normalizeEnum(options.pricingModel ?? "quoted", "--pricing-model", PRICING_MODELS),
232
- required_capabilities: parseCsvList(options.requiredCapabilities),
612
+ required_capabilities: requiredCapabilities.length > 0 ? requiredCapabilities : [kind],
233
613
  inputs: parseCsvList(options.inputs),
234
614
  outputs: parseCsvList(options.outputs),
235
615
  receipt_required: true,
@@ -244,28 +624,146 @@ function runWorkOrderNewCommandLocked(options) {
244
624
  const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
245
625
  const workId = normalizePortableIdRef(options.workId, "--work-id");
246
626
  const workNode = requireReferenceNode(index, ws, workId, "work", "work contract");
247
- const workVersion = String(workNode.attributes.version ?? "0.1.0");
248
- const receipt = createAgentWorkflowNode({
627
+ const created = createWorkOrderForWork({
249
628
  root: options.root,
250
629
  ws,
251
- type: "work_order",
252
630
  title: options.title,
253
631
  id: options.id,
632
+ workId,
633
+ workNode,
634
+ requester: options.requester,
635
+ requestRef: options.requestRef,
636
+ triggerRef: options.triggerRef,
637
+ payloadHash: options.payloadHash,
638
+ inputRefs: options.inputRefs,
639
+ queueRefs: options.queueRefs,
640
+ requestedOutputs: options.requestedOutputs,
641
+ constraintRefs: options.constraintRefs,
642
+ now: options.now,
643
+ });
644
+ printReceipt("order created", created.receipt, options.json);
645
+ }
646
+ function runWorkTriggerCommandLocked(options) {
647
+ const config = (0, config_1.loadConfig)(options.root);
648
+ const ws = normalizeWorkspace(options.ws);
649
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
650
+ const { workNode, sourceNode } = resolveTriggerWorkNode(index, ws, options.targetRef);
651
+ const requester = options.requester ?? "user.local";
652
+ const requestRef = "request.redacted";
653
+ const triggerRef = "trigger.mdkg-work-trigger";
654
+ const requestedOutputs = toStringList(workNode.attributes.outputs);
655
+ const payloadHash = buildWorkOrderPayloadHash({
656
+ workId: workNode.id,
657
+ workVersion: String(workNode.attributes.version ?? "0.1.0"),
658
+ requester,
659
+ requestRef,
660
+ triggerRef,
661
+ inputRefs: [],
662
+ queueRefs: [],
663
+ requestedOutputs,
664
+ constraintRefs: [],
665
+ });
666
+ const id = options.id
667
+ ? normalizePortableId(options.id, "--id")
668
+ : `order.${portableSegment(workNode.id)}.${payloadHash.slice("sha256:".length, "sha256:".length + 12)}`;
669
+ const title = options.title ?? `Trigger ${workNode.title}`;
670
+ const enqueueQueue = normalizeQueueName(options.enqueue);
671
+ const queueRef = enqueueQueue ? queueRefForWorkOrder(enqueueQueue, id) : undefined;
672
+ const queueDatabasePath = enqueueQueue ? loadWorkTriggerQueueDatabase(options.root, enqueueQueue) : undefined;
673
+ const created = createWorkOrderForWork({
674
+ root: options.root,
675
+ ws,
676
+ title,
677
+ id,
678
+ workId: workNode.id,
679
+ workNode,
680
+ requester,
681
+ requestRef,
682
+ triggerRef,
683
+ payloadHash,
684
+ queueRefs: queueRef,
254
685
  now: options.now,
255
- overrides: {
256
- work_id: workId,
257
- work_version: workVersion,
258
- requester: options.requester,
259
- order_status: "submitted",
260
- request_ref: options.requestRef ?? "request.redacted",
261
- input_refs: parseCsvList(options.inputRefs),
262
- requested_outputs: parseCsvList(options.requestedOutputs),
263
- constraint_refs: parseCsvList(options.constraintRefs),
264
- artifact_policy: "commit_sidecar_and_zip",
265
- relates: [workId],
266
- },
267
686
  });
268
- printReceipt("order created", receipt, options.json);
687
+ let queueDelivery;
688
+ if (enqueueQueue && queueDatabasePath && queueRef) {
689
+ const queuePayload = {
690
+ kind: "mdkg.work_order.triggered",
691
+ schema_version: 1,
692
+ target_ref: options.targetRef,
693
+ work_id: workNode.id,
694
+ work_qid: workNode.qid,
695
+ work_order_id: created.receipt.id,
696
+ work_order_qid: created.receipt.qid,
697
+ work_order_path: created.receipt.path,
698
+ requester,
699
+ request_ref: requestRef,
700
+ trigger_ref: triggerRef,
701
+ payload_hash: created.payloadHash,
702
+ queue_ref: queueRef,
703
+ executed: false,
704
+ };
705
+ if (sourceNode) {
706
+ queuePayload.source_qid = sourceNode.qid;
707
+ }
708
+ const delivery = (0, project_db_queue_1.enqueueProjectQueueMessage)(queueDatabasePath, {
709
+ queue_name: enqueueQueue,
710
+ message_id: created.receipt.id,
711
+ dedupe_key: created.receipt.qid,
712
+ payload: queuePayload,
713
+ now_ms: options.now?.getTime(),
714
+ });
715
+ queueDelivery = {
716
+ queue_name: enqueueQueue,
717
+ queue_ref: queueRef,
718
+ message_id: created.receipt.id,
719
+ message: delivery.message,
720
+ created: delivery.created,
721
+ duplicate: delivery.duplicate,
722
+ };
723
+ }
724
+ const event = (0, event_support_1.appendAutomaticEvent)({
725
+ root: options.root,
726
+ ws,
727
+ kind: queueDelivery ? "WORK_TRIGGER_ENQUEUED" : "WORK_TRIGGERED",
728
+ status: "ok",
729
+ refs: [created.receipt.id, workNode.id, ...(queueRef ? [queueRef] : [])],
730
+ notes: queueDelivery
731
+ ? `work trigger created order mirror and enqueued ${queueDelivery.message_id} on project DB queue ${queueDelivery.queue_name}; no work executed`
732
+ : "work trigger created order mirror; no work executed",
733
+ now: options.now,
734
+ });
735
+ if (options.json) {
736
+ console.log(JSON.stringify({
737
+ action: "triggered",
738
+ node: created.receipt,
739
+ trigger: {
740
+ target_ref: options.targetRef,
741
+ source_qid: sourceNode?.qid,
742
+ work_qid: workNode.qid,
743
+ payload_hash: created.payloadHash,
744
+ executed: false,
745
+ enqueue: queueDelivery
746
+ ? {
747
+ requested: true,
748
+ queue_name: queueDelivery.queue_name,
749
+ queue_ref: queueDelivery.queue_ref,
750
+ message_id: queueDelivery.message_id,
751
+ enqueued: true,
752
+ created: queueDelivery.created,
753
+ duplicate: queueDelivery.duplicate,
754
+ message_status: queueDelivery.message.status,
755
+ message_payload_hash: queueDelivery.message.payload_hash,
756
+ }
757
+ : { requested: false },
758
+ event_appended: event !== undefined,
759
+ },
760
+ }, null, 2));
761
+ return;
762
+ }
763
+ console.log(`work triggered: ${created.receipt.qid} (${created.receipt.path})`);
764
+ if (queueDelivery) {
765
+ console.log(`queue enqueued: ${queueDelivery.queue_name}/${queueDelivery.message_id}`);
766
+ }
269
767
  }
270
768
  function runWorkOrderUpdateCommandLocked(options) {
271
769
  const loaded = loadMutableAgentNode(options.root, options.id, options.ws, "work_order");
@@ -273,12 +771,27 @@ function runWorkOrderUpdateCommandLocked(options) {
273
771
  loaded.frontmatter.order_status = normalizeEnum(options.status, "--status", ORDER_STATUSES);
274
772
  }
275
773
  loaded.frontmatter.input_refs = appendUnique(toStringList(loaded.frontmatter.input_refs), parseCsvList(options.addInputRefs));
774
+ loaded.frontmatter.queue_refs = appendUnique(toStringList(loaded.frontmatter.queue_refs), parseCsvList(options.addQueueRefs));
276
775
  loaded.frontmatter.artifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), parseCsvList(options.addArtifacts));
277
776
  loaded.frontmatter.updated = (0, date_1.formatDate)(options.now ?? new Date());
278
777
  writeFrontmatterFile(loaded.filePath, loaded.frontmatter, loaded.body);
279
778
  maybeReindex(options.root, loaded.config);
280
779
  printReceipt("order updated", nodeReceipt(options.root, loaded.node), options.json);
281
780
  }
781
+ function runWorkOrderStatusCommandLocked(options) {
782
+ const config = (0, config_1.loadConfig)(options.root);
783
+ const ws = normalizeWorkspace(options.ws);
784
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
785
+ const order = resolveReadableWorkNode(index, options.id, ws, "work_order", "work order");
786
+ const receipt = buildWorkOrderStatusReceipt(index, order);
787
+ if (options.json) {
788
+ console.log(JSON.stringify(receipt, null, 2));
789
+ return;
790
+ }
791
+ console.log(`${receipt.order.qid}: ${receipt.order.status ?? "unknown"}`);
792
+ console.log(`work: ${receipt.order.work_qid ?? receipt.order.work_id ?? "unknown"}`);
793
+ console.log(`receipts: ${receipt.receipt_count}`);
794
+ }
282
795
  function runWorkReceiptNewCommandLocked(options) {
283
796
  const config = (0, config_1.loadConfig)(options.root);
284
797
  const ws = normalizeWorkspace(options.ws);
@@ -297,11 +810,13 @@ function runWorkReceiptNewCommandLocked(options) {
297
810
  receipt_status: normalizeEnum(options.receiptStatus ?? "recorded", "--receipt-status", RECEIPT_STATUSES),
298
811
  outcome: normalizeEnum(options.outcome, "--outcome", OUTCOMES),
299
812
  cost_ref: options.costRef ?? "cost.redacted",
813
+ redaction_policy: normalizeEnum(options.redactionPolicy ?? "refs_and_hashes_only", "--redaction-policy", REDACTION_POLICIES),
300
814
  artifacts: parseCsvList(options.artifacts),
301
815
  proof_refs: parseCsvList(options.proofRefs),
302
816
  attestation_refs: parseCsvList(options.attestationRefs),
303
- input_hashes: parseCsvList(options.inputHashes),
304
- output_hashes: parseCsvList(options.outputHashes),
817
+ evidence_hashes: normalizeSha256Refs(options.evidenceHashes, "--evidence-hashes"),
818
+ input_hashes: normalizeSha256Refs(options.inputHashes, "--input-hashes"),
819
+ output_hashes: normalizeSha256Refs(options.outputHashes, "--output-hashes"),
305
820
  relates: [workOrderId],
306
821
  },
307
822
  });
@@ -315,11 +830,36 @@ function runWorkReceiptUpdateCommandLocked(options) {
315
830
  loaded.frontmatter.artifacts = appendUnique(toStringList(loaded.frontmatter.artifacts), parseCsvList(options.addArtifacts));
316
831
  loaded.frontmatter.proof_refs = appendUnique(toStringList(loaded.frontmatter.proof_refs), parseCsvList(options.addProofRefs));
317
832
  loaded.frontmatter.attestation_refs = appendUnique(toStringList(loaded.frontmatter.attestation_refs), parseCsvList(options.addAttestationRefs));
833
+ loaded.frontmatter.evidence_hashes = appendUnique(toStringList(loaded.frontmatter.evidence_hashes), normalizeSha256Refs(options.addEvidenceHashes, "--add-evidence-hashes"));
318
834
  loaded.frontmatter.updated = (0, date_1.formatDate)(options.now ?? new Date());
319
835
  writeFrontmatterFile(loaded.filePath, loaded.frontmatter, loaded.body);
320
836
  maybeReindex(options.root, loaded.config);
321
837
  printReceipt("receipt updated", nodeReceipt(options.root, loaded.node), options.json);
322
838
  }
839
+ function runWorkReceiptVerifyCommandLocked(options) {
840
+ const config = (0, config_1.loadConfig)(options.root);
841
+ const ws = normalizeWorkspace(options.ws);
842
+ const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
843
+ const receiptNode = resolveReadableWorkNode(index, options.id, ws, "receipt", "receipt");
844
+ const receipt = buildWorkReceiptVerifyReceipt(index, receiptNode);
845
+ if (options.json) {
846
+ console.log(JSON.stringify(receipt, null, 2));
847
+ if (!receipt.ok) {
848
+ throw new errors_1.ValidationError("receipt verification failed");
849
+ }
850
+ return;
851
+ }
852
+ console.log(`${receipt.receipt.qid}: ${receipt.ok ? "ok" : "failed"}`);
853
+ for (const check of receipt.checks) {
854
+ console.log(`- ${check.name}: ${check.ok ? "ok" : "failed"} - ${check.detail}`);
855
+ }
856
+ for (const warning of receipt.warnings) {
857
+ console.log(`warning: ${warning}`);
858
+ }
859
+ if (!receipt.ok) {
860
+ throw new errors_1.ValidationError("receipt verification failed");
861
+ }
862
+ }
323
863
  function runWorkArtifactAddCommandLocked(options) {
324
864
  const config = (0, config_1.loadConfig)(options.root);
325
865
  const ws = normalizeWorkspace(options.ws);
@@ -388,12 +928,21 @@ function runWorkOrderNewCommand(options) {
388
928
  function runWorkOrderUpdateCommand(options) {
389
929
  return withWorkLock(options.root, () => runWorkOrderUpdateCommandLocked(options));
390
930
  }
931
+ function runWorkOrderStatusCommand(options) {
932
+ return runWorkOrderStatusCommandLocked(options);
933
+ }
934
+ function runWorkTriggerCommand(options) {
935
+ return withWorkLock(options.root, () => runWorkTriggerCommandLocked(options));
936
+ }
391
937
  function runWorkReceiptNewCommand(options) {
392
938
  return withWorkLock(options.root, () => runWorkReceiptNewCommandLocked(options));
393
939
  }
394
940
  function runWorkReceiptUpdateCommand(options) {
395
941
  return withWorkLock(options.root, () => runWorkReceiptUpdateCommandLocked(options));
396
942
  }
943
+ function runWorkReceiptVerifyCommand(options) {
944
+ return runWorkReceiptVerifyCommandLocked(options);
945
+ }
397
946
  function runWorkArtifactAddCommand(options) {
398
947
  return withWorkLock(options.root, () => runWorkArtifactAddCommandLocked(options));
399
948
  }