@mastra/factory 0.9.0 → 0.10.0-alpha.10
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 +179 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/factory.d.ts +8 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +24 -8
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +1 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/provenance.d.ts +7 -2
- package/dist/integrations/github/provenance.d.ts.map +1 -1
- package/dist/integrations/github/provenance.js +16 -2
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +3 -0
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +165 -6
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +193 -127
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +50 -0
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +1 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/routes/attention.d.ts +17 -0
- package/dist/routes/attention.d.ts.map +1 -0
- package/dist/routes/attention.js +305 -0
- package/dist/routes/attention.js.map +1 -0
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +11 -2
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/surface.d.ts +2 -3
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +61 -43
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +35 -14
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.js +5 -5
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts +13 -0
- package/dist/rules/dispatch-errors.d.ts.map +1 -0
- package/dist/rules/dispatch-errors.js +77 -0
- package/dist/rules/dispatch-errors.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +9 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +118 -45
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +30 -14
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/terminal-cleanup.d.ts +1 -1
- package/dist/rules/terminal-cleanup.d.ts.map +1 -1
- package/dist/rules/terminal-cleanup.js +2 -2
- package/dist/rules/terminal-cleanup.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -1
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +1 -1
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +2 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +9 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/secret-encryption.d.ts +25 -0
- package/dist/secret-encryption.d.ts.map +1 -0
- package/dist/secret-encryption.js +96 -0
- package/dist/secret-encryption.js.map +1 -0
- package/dist/session/factory-session.d.ts +13 -3
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js +15 -6
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +16 -10
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +74 -14
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/session/first-message-capture.d.ts +14 -10
- package/dist/session/first-message-capture.d.ts.map +1 -1
- package/dist/session/first-message-capture.js +17 -11
- package/dist/session/first-message-capture.js.map +1 -1
- package/dist/session/session-title.d.ts +7 -0
- package/dist/session/session-title.d.ts.map +1 -0
- package/dist/session/session-title.js +14 -0
- package/dist/session/session-title.js.map +1 -0
- package/dist/session/thread-title-mirror.d.ts +35 -0
- package/dist/session/thread-title-mirror.d.ts.map +1 -0
- package/dist/session/thread-title-mirror.js +51 -0
- package/dist/session/thread-title-mirror.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +3 -1
- package/dist/storage/domains/credentials/base.d.ts.map +1 -1
- package/dist/storage/domains/credentials/base.js +33 -15
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.d.ts +3 -1
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +45 -25
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +6 -5
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +21 -13
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +3 -1
- package/dist/storage/domains/integrations/base.d.ts.map +1 -1
- package/dist/storage/domains/integrations/base.js +28 -12
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +8 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +14 -2
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +7 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +115 -12
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +497 -43
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/work-item-branch.d.ts +23 -0
- package/dist/work-item-branch.d.ts.map +1 -0
- package/dist/work-item-branch.js +39 -0
- package/dist/work-item-branch.js.map +1 -0
- package/factory-skills/factory-rereview/SKILL.md +2 -0
- package/factory-skills/factory-review/SKILL.md +2 -0
- package/package.json +7 -7
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTerminalFactoryRuleStage } from "../../../rules/types.js";
|
|
1
2
|
import { createHash } from "crypto";
|
|
2
3
|
import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
|
|
3
4
|
//#region src/storage/domains/work-items/base.ts
|
|
@@ -20,6 +21,7 @@ function factoryDecisionHash(decision) {
|
|
|
20
21
|
}
|
|
21
22
|
/** Dispatcher upsert idempotency token — server bookkeeping, dropped from the read wire. */
|
|
22
23
|
const FACTORY_RULE_MATERIALIZATION_KEY = "factoryRuleMaterializationKey";
|
|
24
|
+
const FACTORY_PULL_REQUEST_RECONCILIATION_KEY = "factoryPullRequestReconciliation";
|
|
23
25
|
/**
|
|
24
26
|
* Whether an actor id marks a move an agent run performed: the binding id the
|
|
25
27
|
* transition tool stamps (`agent:*`), or the rule that fires off a bound run's
|
|
@@ -34,6 +36,35 @@ function isAgentActor(by) {
|
|
|
34
36
|
if (by === void 0) return false;
|
|
35
37
|
return by.startsWith("agent:") || by === "factory-tool-result-rule";
|
|
36
38
|
}
|
|
39
|
+
const FACTORY_DISPATCH_FAILURE_CODES = [
|
|
40
|
+
"session_unavailable",
|
|
41
|
+
"source_control_missing",
|
|
42
|
+
"source_repository_missing",
|
|
43
|
+
"unsupported_provider_item",
|
|
44
|
+
"notification_delivery_failed",
|
|
45
|
+
"repository_git_missing",
|
|
46
|
+
"repository_egress_blocked",
|
|
47
|
+
"repository_clone_failed",
|
|
48
|
+
"repository_pull_failed",
|
|
49
|
+
"repository_push_failed",
|
|
50
|
+
"repository_commit_failed",
|
|
51
|
+
"repository_cli_missing",
|
|
52
|
+
"repository_pr_failed",
|
|
53
|
+
"unknown"
|
|
54
|
+
];
|
|
55
|
+
function isFactoryDispatchFailureCode(value) {
|
|
56
|
+
return FACTORY_DISPATCH_FAILURE_CODES.some((code) => code === value);
|
|
57
|
+
}
|
|
58
|
+
function factoryDecisionAttentionIdentity(decisionId, failureOccurrence) {
|
|
59
|
+
return {
|
|
60
|
+
kind: "automation-failed",
|
|
61
|
+
sourceId: decisionId,
|
|
62
|
+
occurrence: failureOccurrence
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function factoryAttentionKey(factoryProjectId, identity) {
|
|
66
|
+
return `factory:${factoryProjectId}:attention:${identity.kind}:${identity.sourceId}:${identity.occurrence}`;
|
|
67
|
+
}
|
|
37
68
|
/**
|
|
38
69
|
* Stages in which a bound run can still act on its work item. Mirrors the
|
|
39
70
|
* non-terminal subset of `FACTORY_RULE_STAGES` (rules/types.ts); bindings for
|
|
@@ -89,11 +120,12 @@ const WORK_ITEMS_SCHEMA = {
|
|
|
89
120
|
columns: ["factory_project_id", "source_key"]
|
|
90
121
|
}],
|
|
91
122
|
indexes: [{
|
|
92
|
-
name: "
|
|
123
|
+
name: "work_items_org_project_created_at_id_idx",
|
|
93
124
|
columns: [
|
|
94
125
|
"org_id",
|
|
95
126
|
"factory_project_id",
|
|
96
|
-
"
|
|
127
|
+
"created_at",
|
|
128
|
+
"id"
|
|
97
129
|
]
|
|
98
130
|
}, {
|
|
99
131
|
name: "work_items_project_parent_idx",
|
|
@@ -223,6 +255,8 @@ function applyUpdate({ current, userId, input }) {
|
|
|
223
255
|
updated_at: now
|
|
224
256
|
};
|
|
225
257
|
}
|
|
258
|
+
const ATTENTION_RECEIPT_QUERY_BATCH_SIZE = 200;
|
|
259
|
+
const ATTENTION_RECEIPT_WRITE_BATCH_SIZE = 25;
|
|
226
260
|
const projectRelationLocks = /* @__PURE__ */ new Map();
|
|
227
261
|
function withInProcessProjectLock(key, fn) {
|
|
228
262
|
const result = (projectRelationLocks.get(key) ?? Promise.resolve()).then(fn, fn);
|
|
@@ -240,6 +274,13 @@ function decisionSourceKey(decision) {
|
|
|
240
274
|
if (record.type !== "upsertLinkedWorkItem" || typeof record.sourceKey !== "string") return null;
|
|
241
275
|
return record.sourceKey;
|
|
242
276
|
}
|
|
277
|
+
function deferredDecisionType(decision) {
|
|
278
|
+
return typeof decision.decision.type === "string" ? decision.decision.type : void 0;
|
|
279
|
+
}
|
|
280
|
+
function ingressWasAccepted(row) {
|
|
281
|
+
if (!row || typeof row.result !== "object" || row.result === null || Array.isArray(row.result)) return false;
|
|
282
|
+
return "status" in row.result && row.result.status === "accepted";
|
|
283
|
+
}
|
|
243
284
|
const FACTORY_GOVERNANCE_SCHEMAS = [
|
|
244
285
|
{
|
|
245
286
|
name: "factory_rule_ingress",
|
|
@@ -315,6 +356,14 @@ const FACTORY_GOVERNANCE_SCHEMAS = [
|
|
|
315
356
|
decision: { type: "json" },
|
|
316
357
|
status: { type: "text" },
|
|
317
358
|
attempts: { type: "integer" },
|
|
359
|
+
delivery_generation: {
|
|
360
|
+
type: "integer",
|
|
361
|
+
default: 0
|
|
362
|
+
},
|
|
363
|
+
failure_occurrence: {
|
|
364
|
+
type: "integer",
|
|
365
|
+
default: 0
|
|
366
|
+
},
|
|
318
367
|
available_at: { type: "timestamp" },
|
|
319
368
|
lease_owner: {
|
|
320
369
|
type: "text",
|
|
@@ -328,10 +377,18 @@ const FACTORY_GOVERNANCE_SCHEMAS = [
|
|
|
328
377
|
type: "text",
|
|
329
378
|
nullable: true
|
|
330
379
|
},
|
|
380
|
+
failure_code: {
|
|
381
|
+
type: "text",
|
|
382
|
+
nullable: true
|
|
383
|
+
},
|
|
331
384
|
approved_at: {
|
|
332
385
|
type: "timestamp",
|
|
333
386
|
nullable: true
|
|
334
387
|
},
|
|
388
|
+
approved_by: {
|
|
389
|
+
type: "text",
|
|
390
|
+
nullable: true
|
|
391
|
+
},
|
|
335
392
|
completed_at: {
|
|
336
393
|
type: "timestamp",
|
|
337
394
|
nullable: true
|
|
@@ -347,9 +404,80 @@ const FACTORY_GOVERNANCE_SCHEMAS = [
|
|
|
347
404
|
"idempotency_key"
|
|
348
405
|
]
|
|
349
406
|
}],
|
|
407
|
+
indexes: [
|
|
408
|
+
{
|
|
409
|
+
name: "factory_deferred_decisions_claim_idx",
|
|
410
|
+
columns: ["status", "created_at"]
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "factory_deferred_decisions_tenant_status_created_idx",
|
|
414
|
+
columns: [
|
|
415
|
+
"org_id",
|
|
416
|
+
"factory_project_id",
|
|
417
|
+
"status",
|
|
418
|
+
"created_at",
|
|
419
|
+
"id"
|
|
420
|
+
]
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "factory_deferred_decisions_tenant_status_failed_idx",
|
|
424
|
+
columns: [
|
|
425
|
+
"org_id",
|
|
426
|
+
"factory_project_id",
|
|
427
|
+
"status",
|
|
428
|
+
"updated_at",
|
|
429
|
+
"id"
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "factory_attention_receipts",
|
|
436
|
+
columns: {
|
|
437
|
+
id: { type: "uuid-pk" },
|
|
438
|
+
org_id: { type: "text" },
|
|
439
|
+
factory_project_id: { type: "text" },
|
|
440
|
+
user_id: { type: "text" },
|
|
441
|
+
kind: { type: "text" },
|
|
442
|
+
source_id: { type: "text" },
|
|
443
|
+
occurrence: { type: "integer" },
|
|
444
|
+
state: { type: "text" },
|
|
445
|
+
read_at: { type: "timestamp" },
|
|
446
|
+
archived_at: {
|
|
447
|
+
type: "timestamp",
|
|
448
|
+
nullable: true
|
|
449
|
+
},
|
|
450
|
+
created_at: { type: "timestamp" },
|
|
451
|
+
updated_at: { type: "timestamp" }
|
|
452
|
+
},
|
|
453
|
+
uniqueIndexes: [{
|
|
454
|
+
name: "factory_attention_receipts_user_source_unique",
|
|
455
|
+
columns: [
|
|
456
|
+
"org_id",
|
|
457
|
+
"factory_project_id",
|
|
458
|
+
"user_id",
|
|
459
|
+
"kind",
|
|
460
|
+
"source_id",
|
|
461
|
+
"occurrence"
|
|
462
|
+
]
|
|
463
|
+
}],
|
|
350
464
|
indexes: [{
|
|
351
|
-
name: "
|
|
352
|
-
columns: [
|
|
465
|
+
name: "factory_attention_receipts_user_state_idx",
|
|
466
|
+
columns: [
|
|
467
|
+
"org_id",
|
|
468
|
+
"factory_project_id",
|
|
469
|
+
"user_id",
|
|
470
|
+
"state"
|
|
471
|
+
]
|
|
472
|
+
}, {
|
|
473
|
+
name: "factory_attention_receipts_source_idx",
|
|
474
|
+
columns: [
|
|
475
|
+
"org_id",
|
|
476
|
+
"factory_project_id",
|
|
477
|
+
"kind",
|
|
478
|
+
"source_id",
|
|
479
|
+
"occurrence"
|
|
480
|
+
]
|
|
353
481
|
}]
|
|
354
482
|
},
|
|
355
483
|
{
|
|
@@ -477,16 +605,52 @@ function toDeferredDecision(row) {
|
|
|
477
605
|
decision: row.decision,
|
|
478
606
|
status: row.status,
|
|
479
607
|
attempts: Number(row.attempts),
|
|
608
|
+
deliveryGeneration: Number(row.delivery_generation ?? 0),
|
|
609
|
+
failureOccurrence: Number(row.failure_occurrence ?? 0),
|
|
480
610
|
availableAt: row.available_at,
|
|
481
611
|
leaseOwner: row.lease_owner ?? null,
|
|
482
612
|
leaseExpiresAt: row.lease_expires_at ?? null,
|
|
483
613
|
lastError: row.last_error ?? null,
|
|
614
|
+
failureCode: isFactoryDispatchFailureCode(row.failure_code) ? row.failure_code : null,
|
|
484
615
|
approvedAt: row.approved_at ?? null,
|
|
616
|
+
approvedBy: row.approved_by ?? null,
|
|
485
617
|
completedAt: row.completed_at ?? null,
|
|
486
618
|
createdAt: row.created_at,
|
|
487
619
|
updatedAt: row.updated_at
|
|
488
620
|
};
|
|
489
621
|
}
|
|
622
|
+
function attentionReceiptKind(value) {
|
|
623
|
+
if (value === "automation-failed") return value;
|
|
624
|
+
throw new Error(`Unsupported attention receipt kind '${String(value)}'.`);
|
|
625
|
+
}
|
|
626
|
+
function attentionReceiptState(value) {
|
|
627
|
+
if (value === "read" || value === "archived") return value;
|
|
628
|
+
throw new Error(`Unsupported attention receipt state '${String(value)}'.`);
|
|
629
|
+
}
|
|
630
|
+
function toAttentionReceipt(row) {
|
|
631
|
+
if (typeof row.source_id !== "string" || !row.source_id) throw new Error("Attention receipt source_id must be a non-empty string.");
|
|
632
|
+
const occurrence = Number(row.occurrence);
|
|
633
|
+
if (!Number.isSafeInteger(occurrence) || occurrence < 0) throw new Error("Attention receipt occurrence must be a non-negative safe integer.");
|
|
634
|
+
if (!(row.read_at instanceof Date)) throw new Error("Attention receipt read_at must be a timestamp.");
|
|
635
|
+
const archivedAt = row.archived_at;
|
|
636
|
+
if (archivedAt !== null && archivedAt !== void 0 && !(archivedAt instanceof Date)) throw new Error("Attention receipt archived_at must be a timestamp or null.");
|
|
637
|
+
if (!(row.created_at instanceof Date)) throw new Error("Attention receipt created_at must be a timestamp.");
|
|
638
|
+
if (!(row.updated_at instanceof Date)) throw new Error("Attention receipt updated_at must be a timestamp.");
|
|
639
|
+
return {
|
|
640
|
+
id: row.id,
|
|
641
|
+
orgId: row.org_id,
|
|
642
|
+
factoryProjectId: String(row.factory_project_id),
|
|
643
|
+
userId: String(row.user_id),
|
|
644
|
+
kind: attentionReceiptKind(row.kind),
|
|
645
|
+
sourceId: row.source_id,
|
|
646
|
+
occurrence,
|
|
647
|
+
state: attentionReceiptState(row.state),
|
|
648
|
+
readAt: row.read_at,
|
|
649
|
+
archivedAt: archivedAt ?? null,
|
|
650
|
+
createdAt: row.created_at,
|
|
651
|
+
updatedAt: row.updated_at
|
|
652
|
+
};
|
|
653
|
+
}
|
|
490
654
|
function toPendingStart(row) {
|
|
491
655
|
return {
|
|
492
656
|
id: row.id,
|
|
@@ -512,6 +676,7 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
512
676
|
}
|
|
513
677
|
async init() {
|
|
514
678
|
await this.ensureCollections([WORK_ITEMS_SCHEMA, ...FACTORY_GOVERNANCE_SCHEMAS]);
|
|
679
|
+
await this.repairLegacyAttentionState();
|
|
515
680
|
}
|
|
516
681
|
async dangerouslyClearAll() {
|
|
517
682
|
await this.ops.deleteMany("work_items", {});
|
|
@@ -519,6 +684,11 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
519
684
|
get #db() {
|
|
520
685
|
return this.ops;
|
|
521
686
|
}
|
|
687
|
+
async #countRows(collection, where) {
|
|
688
|
+
const count = this.#db.count;
|
|
689
|
+
if (!count) throw new Error("[WorkItemsStorage] storage backend does not support collection counts.");
|
|
690
|
+
return count.call(this.#db, collection, where);
|
|
691
|
+
}
|
|
522
692
|
async #withProjectRelationTransaction(orgId, factoryProjectId, fn) {
|
|
523
693
|
return withInProcessProjectLock(`work-items:${orgId}:${factoryProjectId}`, () => this.storage.withTransaction(fn, { isolationLevel: "serializable" }));
|
|
524
694
|
}
|
|
@@ -608,11 +778,14 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
608
778
|
failed = true;
|
|
609
779
|
return {
|
|
610
780
|
status: input.terminal ? "failed" : "retry",
|
|
781
|
+
...table === "factory_deferred_decisions" && !input.terminal && input.advanceDeliveryGeneration !== false ? { delivery_generation: Number(current.delivery_generation ?? 0) + 1 } : {},
|
|
611
782
|
available_at: input.availableAt,
|
|
612
783
|
lease_owner: null,
|
|
613
784
|
lease_expires_at: null,
|
|
614
785
|
last_error: input.lastError,
|
|
786
|
+
...table === "factory_deferred_decisions" ? { failure_code: input.failureCode } : {},
|
|
615
787
|
completed_at: input.terminal ? input.now : null,
|
|
788
|
+
...table === "factory_deferred_decisions" && input.terminal ? { failure_occurrence: Number(current.failure_occurrence ?? 0) + 1 } : {},
|
|
616
789
|
updated_at: input.now
|
|
617
790
|
};
|
|
618
791
|
});
|
|
@@ -622,12 +795,24 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
622
795
|
return (await ops.findMany("work_items", {
|
|
623
796
|
org_id: orgId,
|
|
624
797
|
factory_project_id: factoryProjectId
|
|
625
|
-
}, { orderBy: [["
|
|
798
|
+
}, { orderBy: [["created_at", "desc"], ["id", "desc"]] })).map(toWorkItem);
|
|
626
799
|
}
|
|
627
|
-
/**
|
|
800
|
+
/**
|
|
801
|
+
* List the org's work items for a project, newest first. Ordered on `created_at` with an id
|
|
802
|
+
* tiebreak so the order is stable: `updated_at` moves under every write, which makes it useless
|
|
803
|
+
* as a cursor and non-deterministic for the callers that iterate this list.
|
|
804
|
+
*/
|
|
628
805
|
async list({ orgId, factoryProjectId }) {
|
|
629
806
|
return this.#listWithOps(this.#db, orgId, factoryProjectId);
|
|
630
807
|
}
|
|
808
|
+
async listByIds({ orgId, factoryProjectId, ids }) {
|
|
809
|
+
if (ids.length === 0) return [];
|
|
810
|
+
return (await this.#db.findMany("work_items", {
|
|
811
|
+
org_id: orgId,
|
|
812
|
+
factory_project_id: factoryProjectId,
|
|
813
|
+
id: { in: [...new Set(ids)] }
|
|
814
|
+
})).map(toWorkItem);
|
|
815
|
+
}
|
|
631
816
|
async get({ orgId, id }) {
|
|
632
817
|
const row = await this.#db.findOne("work_items", {
|
|
633
818
|
org_id: orgId,
|
|
@@ -749,6 +934,7 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
749
934
|
decision,
|
|
750
935
|
status: "pending",
|
|
751
936
|
attempts: 0,
|
|
937
|
+
delivery_generation: 0,
|
|
752
938
|
available_at: now,
|
|
753
939
|
lease_owner: null,
|
|
754
940
|
lease_expires_at: null,
|
|
@@ -865,6 +1051,7 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
865
1051
|
decision,
|
|
866
1052
|
status: "pending",
|
|
867
1053
|
attempts: 0,
|
|
1054
|
+
delivery_generation: 0,
|
|
868
1055
|
available_at: input.now,
|
|
869
1056
|
lease_owner: null,
|
|
870
1057
|
lease_expires_at: null,
|
|
@@ -934,6 +1121,143 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
934
1121
|
hasMore: rows.length > input.limit
|
|
935
1122
|
};
|
|
936
1123
|
}
|
|
1124
|
+
async listFailedDecisionPage(input) {
|
|
1125
|
+
const rows = await this.#db.findMany("factory_deferred_decisions", {
|
|
1126
|
+
org_id: input.orgId,
|
|
1127
|
+
factory_project_id: input.factoryProjectId,
|
|
1128
|
+
status: "failed"
|
|
1129
|
+
}, {
|
|
1130
|
+
orderBy: [["updated_at", "desc"], ["id", "desc"]],
|
|
1131
|
+
limit: input.limit + 1,
|
|
1132
|
+
...input.before ? { cursor: { values: [input.before.occurredAt, input.before.id] } } : {}
|
|
1133
|
+
});
|
|
1134
|
+
return {
|
|
1135
|
+
decisions: rows.slice(0, input.limit).map(toDeferredDecision),
|
|
1136
|
+
hasMore: rows.length > input.limit
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
async countDeferredDecisionsByStatuses({ orgId, factoryProjectId, statuses }) {
|
|
1140
|
+
return this.#countRows("factory_deferred_decisions", {
|
|
1141
|
+
org_id: orgId,
|
|
1142
|
+
factory_project_id: factoryProjectId,
|
|
1143
|
+
status: { in: statuses }
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
async getDeferredDecision(orgId, factoryProjectId, decisionId) {
|
|
1147
|
+
const row = await this.#db.findOne("factory_deferred_decisions", {
|
|
1148
|
+
id: decisionId,
|
|
1149
|
+
org_id: orgId,
|
|
1150
|
+
factory_project_id: factoryProjectId
|
|
1151
|
+
});
|
|
1152
|
+
return row ? toDeferredDecision(row) : null;
|
|
1153
|
+
}
|
|
1154
|
+
async listAttentionReceipts({ orgId, factoryProjectId, userId, identities }) {
|
|
1155
|
+
if (identities.length === 0) return [];
|
|
1156
|
+
const requested = new Set(identities.map((identity) => `${identity.kind}\0${identity.sourceId}\0${identity.occurrence}`));
|
|
1157
|
+
const sourceIds = [...new Set(identities.map((identity) => identity.sourceId))];
|
|
1158
|
+
const kinds = [...new Set(identities.map((identity) => identity.kind))];
|
|
1159
|
+
const receipts = [];
|
|
1160
|
+
for (let index = 0; index < sourceIds.length; index += ATTENTION_RECEIPT_QUERY_BATCH_SIZE) {
|
|
1161
|
+
const rows = await this.#db.findMany("factory_attention_receipts", {
|
|
1162
|
+
org_id: orgId,
|
|
1163
|
+
factory_project_id: factoryProjectId,
|
|
1164
|
+
user_id: userId,
|
|
1165
|
+
kind: { in: kinds },
|
|
1166
|
+
source_id: { in: sourceIds.slice(index, index + ATTENTION_RECEIPT_QUERY_BATCH_SIZE) }
|
|
1167
|
+
});
|
|
1168
|
+
receipts.push(...rows.map(toAttentionReceipt).filter((receipt) => requested.has(`${receipt.kind}\0${receipt.sourceId}\0${receipt.occurrence}`)));
|
|
1169
|
+
}
|
|
1170
|
+
return receipts;
|
|
1171
|
+
}
|
|
1172
|
+
async countAttentionReceipts({ orgId, factoryProjectId, userId, state }) {
|
|
1173
|
+
return this.#countRows("factory_attention_receipts", {
|
|
1174
|
+
org_id: orgId,
|
|
1175
|
+
factory_project_id: factoryProjectId,
|
|
1176
|
+
user_id: userId,
|
|
1177
|
+
kind: "automation-failed",
|
|
1178
|
+
...state ? { state } : {}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
async deleteAttentionReceipts({ orgId, factoryProjectId, identities }) {
|
|
1182
|
+
const unique = [...new Map(identities.map((identity) => [`${identity.kind}\0${identity.sourceId}\0${identity.occurrence}`, identity])).values()];
|
|
1183
|
+
for (let index = 0; index < unique.length; index += ATTENTION_RECEIPT_WRITE_BATCH_SIZE) await Promise.all(unique.slice(index, index + ATTENTION_RECEIPT_WRITE_BATCH_SIZE).map((identity) => this.#db.deleteMany("factory_attention_receipts", {
|
|
1184
|
+
org_id: orgId,
|
|
1185
|
+
factory_project_id: factoryProjectId,
|
|
1186
|
+
kind: identity.kind,
|
|
1187
|
+
source_id: identity.sourceId,
|
|
1188
|
+
occurrence: identity.occurrence
|
|
1189
|
+
})));
|
|
1190
|
+
}
|
|
1191
|
+
async setAttentionReceipt(input) {
|
|
1192
|
+
return this.#retryAttentionReceiptWrite(() => this.storage.withTransaction((ops) => this.#setAttentionReceiptWithOps(ops, input)));
|
|
1193
|
+
}
|
|
1194
|
+
async #retryAttentionReceiptWrite(write) {
|
|
1195
|
+
try {
|
|
1196
|
+
return await write();
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
if (!(error instanceof UniqueViolationError)) throw error;
|
|
1199
|
+
return write();
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
async #setAttentionReceiptWithOps(ops, { orgId, factoryProjectId, userId, decisionId, failureOccurrence, action, now }) {
|
|
1203
|
+
const identity = factoryDecisionAttentionIdentity(decisionId, failureOccurrence);
|
|
1204
|
+
let currentOccurrence = false;
|
|
1205
|
+
if (!await ops.updateAtomic("factory_deferred_decisions", {
|
|
1206
|
+
id: decisionId,
|
|
1207
|
+
org_id: orgId,
|
|
1208
|
+
factory_project_id: factoryProjectId
|
|
1209
|
+
}, (current) => {
|
|
1210
|
+
currentOccurrence = current.status === "failed" && Number(current.failure_occurrence ?? 0) === failureOccurrence;
|
|
1211
|
+
return null;
|
|
1212
|
+
}) || !currentOccurrence) return null;
|
|
1213
|
+
const where = {
|
|
1214
|
+
org_id: orgId,
|
|
1215
|
+
factory_project_id: factoryProjectId,
|
|
1216
|
+
user_id: userId,
|
|
1217
|
+
kind: identity.kind,
|
|
1218
|
+
source_id: identity.sourceId,
|
|
1219
|
+
occurrence: identity.occurrence
|
|
1220
|
+
};
|
|
1221
|
+
const existing = await ops.findOne("factory_attention_receipts", where);
|
|
1222
|
+
if (!existing) return toAttentionReceipt(await ops.insertOne("factory_attention_receipts", {
|
|
1223
|
+
...where,
|
|
1224
|
+
state: action === "archive" ? "archived" : "read",
|
|
1225
|
+
read_at: now,
|
|
1226
|
+
archived_at: action === "archive" ? now : null,
|
|
1227
|
+
created_at: now,
|
|
1228
|
+
updated_at: now
|
|
1229
|
+
}));
|
|
1230
|
+
const row = await ops.updateAtomic("factory_attention_receipts", {
|
|
1231
|
+
id: existing.id,
|
|
1232
|
+
...where
|
|
1233
|
+
}, (current) => {
|
|
1234
|
+
const preserveArchive = action === "read" && current.state === "archived";
|
|
1235
|
+
const archived = action === "archive" || preserveArchive;
|
|
1236
|
+
return {
|
|
1237
|
+
state: archived ? "archived" : "read",
|
|
1238
|
+
read_at: current.read_at,
|
|
1239
|
+
archived_at: action === "archive" ? now : archived ? current.archived_at ?? now : null,
|
|
1240
|
+
updated_at: now
|
|
1241
|
+
};
|
|
1242
|
+
});
|
|
1243
|
+
return row ? toAttentionReceipt(row) : null;
|
|
1244
|
+
}
|
|
1245
|
+
async markAttentionReceiptsRead({ orgId, factoryProjectId, userId, occurrences, now }) {
|
|
1246
|
+
const uniqueOccurrences = [...new Map(occurrences.map((occurrence) => [`${occurrence.decisionId}:${occurrence.failureOccurrence}`, occurrence])).values()];
|
|
1247
|
+
for (let index = 0; index < uniqueOccurrences.length; index += ATTENTION_RECEIPT_WRITE_BATCH_SIZE) {
|
|
1248
|
+
const batch = uniqueOccurrences.slice(index, index + ATTENTION_RECEIPT_WRITE_BATCH_SIZE);
|
|
1249
|
+
await this.#retryAttentionReceiptWrite(() => this.storage.withTransaction(async (ops) => {
|
|
1250
|
+
for (const occurrence of batch) await this.#setAttentionReceiptWithOps(ops, {
|
|
1251
|
+
orgId,
|
|
1252
|
+
factoryProjectId,
|
|
1253
|
+
userId,
|
|
1254
|
+
...occurrence,
|
|
1255
|
+
action: "read",
|
|
1256
|
+
now
|
|
1257
|
+
});
|
|
1258
|
+
}));
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
937
1261
|
async claimDeferredDecisions(input) {
|
|
938
1262
|
return this.#claimLeases("factory_deferred_decisions", input, toDeferredDecision);
|
|
939
1263
|
}
|
|
@@ -974,7 +1298,7 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
974
1298
|
* is armed in the same transaction — a crash cannot release the run while
|
|
975
1299
|
* leaving its follow-up work parked for re-approval.
|
|
976
1300
|
*/
|
|
977
|
-
async approveDeferredDecision(orgId, factoryProjectId, decisionId, now) {
|
|
1301
|
+
async approveDeferredDecision(orgId, factoryProjectId, decisionId, now, approvedBy) {
|
|
978
1302
|
return this.storage.withTransaction(async (ops) => {
|
|
979
1303
|
let settled = false;
|
|
980
1304
|
const row = await ops.updateAtomic("factory_deferred_decisions", {
|
|
@@ -989,6 +1313,7 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
989
1313
|
attempts: 0,
|
|
990
1314
|
available_at: now,
|
|
991
1315
|
approved_at: now,
|
|
1316
|
+
approved_by: approvedBy ?? null,
|
|
992
1317
|
updated_at: now
|
|
993
1318
|
};
|
|
994
1319
|
});
|
|
@@ -1014,27 +1339,38 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
1014
1339
|
});
|
|
1015
1340
|
}
|
|
1016
1341
|
/**
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
* and they would otherwise sit on the card forever asking to be answered.
|
|
1020
|
-
*
|
|
1021
|
-
* Pass `role` to retire only the proposals a run now starting has overtaken:
|
|
1022
|
-
* a parked "start triage" is moot the moment triage is actually running.
|
|
1342
|
+
* Automatically retire proposed or failed work that a newer run or terminal
|
|
1343
|
+
* work-item state has overtaken. Human dismissal remains `dismissed`.
|
|
1023
1344
|
*/
|
|
1024
|
-
async
|
|
1345
|
+
async supersedeDecisionsForWorkItem(input) {
|
|
1025
1346
|
const rows = await this.#db.findMany("factory_deferred_decisions", {
|
|
1026
1347
|
org_id: input.orgId,
|
|
1027
1348
|
factory_project_id: input.factoryProjectId,
|
|
1028
1349
|
work_item_id: input.workItemId,
|
|
1029
|
-
status: "proposed"
|
|
1350
|
+
status: { in: ["proposed", "failed"] }
|
|
1030
1351
|
});
|
|
1031
|
-
const
|
|
1352
|
+
const superseded = [];
|
|
1032
1353
|
for (const row of rows) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1354
|
+
const decision = toDeferredDecision(row);
|
|
1355
|
+
if (input.role !== void 0 && decision.decision.role !== input.role) continue;
|
|
1356
|
+
const record = decision.status === "proposed" ? await this.#settleProposedDecision({
|
|
1357
|
+
orgId: input.orgId,
|
|
1358
|
+
factoryProjectId: input.factoryProjectId,
|
|
1359
|
+
decisionId: decision.id
|
|
1360
|
+
}, {
|
|
1361
|
+
status: "superseded",
|
|
1362
|
+
updated_at: input.supersededAt,
|
|
1363
|
+
completed_at: input.supersededAt
|
|
1364
|
+
}) : await this.#resolveFailedDecision({
|
|
1365
|
+
orgId: input.orgId,
|
|
1366
|
+
factoryProjectId: input.factoryProjectId,
|
|
1367
|
+
decisionId: decision.id,
|
|
1368
|
+
status: "superseded",
|
|
1369
|
+
now: input.supersededAt
|
|
1370
|
+
});
|
|
1371
|
+
if (record) superseded.push(record);
|
|
1036
1372
|
}
|
|
1037
|
-
return
|
|
1373
|
+
return superseded;
|
|
1038
1374
|
}
|
|
1039
1375
|
async #settleProposedDecision({ orgId, factoryProjectId, decisionId }, patch) {
|
|
1040
1376
|
let settled = false;
|
|
@@ -1049,28 +1385,119 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
1049
1385
|
});
|
|
1050
1386
|
return settled && row ? toDeferredDecision(row) : null;
|
|
1051
1387
|
}
|
|
1388
|
+
async #resolveFailedDecision({ orgId, factoryProjectId, decisionId, status, now }) {
|
|
1389
|
+
return this.storage.withTransaction(async (ops) => {
|
|
1390
|
+
let resolved = false;
|
|
1391
|
+
const row = await ops.updateAtomic("factory_deferred_decisions", {
|
|
1392
|
+
id: decisionId,
|
|
1393
|
+
org_id: orgId,
|
|
1394
|
+
factory_project_id: factoryProjectId
|
|
1395
|
+
}, (current) => {
|
|
1396
|
+
if (current.status !== "failed") return null;
|
|
1397
|
+
resolved = true;
|
|
1398
|
+
return {
|
|
1399
|
+
status,
|
|
1400
|
+
updated_at: now
|
|
1401
|
+
};
|
|
1402
|
+
});
|
|
1403
|
+
if (!resolved || !row) return null;
|
|
1404
|
+
await ops.deleteMany("factory_attention_receipts", {
|
|
1405
|
+
org_id: orgId,
|
|
1406
|
+
factory_project_id: factoryProjectId,
|
|
1407
|
+
kind: "automation-failed",
|
|
1408
|
+
source_id: decisionId,
|
|
1409
|
+
occurrence: Number(row.failure_occurrence ?? 0)
|
|
1410
|
+
});
|
|
1411
|
+
return toDeferredDecision(row);
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
async supersedeTerminalDecisionsForWorkItem(input) {
|
|
1415
|
+
await this.supersedeDecisionsForWorkItem(input);
|
|
1416
|
+
}
|
|
1417
|
+
async repairLegacyAttentionState() {
|
|
1418
|
+
let cursor;
|
|
1419
|
+
const inspectedWorkItems = /* @__PURE__ */ new Set();
|
|
1420
|
+
while (true) {
|
|
1421
|
+
const rows = await this.#db.findMany("factory_deferred_decisions", { status: { in: ["failed", "proposed"] } }, {
|
|
1422
|
+
orderBy: [["created_at", "asc"], ["id", "asc"]],
|
|
1423
|
+
limit: 201,
|
|
1424
|
+
...cursor ? { cursor } : {}
|
|
1425
|
+
});
|
|
1426
|
+
const page = rows.slice(0, ATTENTION_RECEIPT_QUERY_BATCH_SIZE);
|
|
1427
|
+
for (const row of page) {
|
|
1428
|
+
const decision = toDeferredDecision(row);
|
|
1429
|
+
if (decision.status === "failed" && decision.failureOccurrence === 0 && deferredDecisionType(decision) === "transition") {
|
|
1430
|
+
if (ingressWasAccepted(await this.#db.findOne("factory_rule_ingress", {
|
|
1431
|
+
org_id: decision.orgId,
|
|
1432
|
+
factory_project_id: decision.factoryProjectId,
|
|
1433
|
+
identity: `decision:${decision.idempotencyKey}`
|
|
1434
|
+
}))) {
|
|
1435
|
+
await this.#resolveFailedDecision({
|
|
1436
|
+
orgId: decision.orgId,
|
|
1437
|
+
factoryProjectId: decision.factoryProjectId,
|
|
1438
|
+
decisionId: decision.id,
|
|
1439
|
+
status: "succeeded",
|
|
1440
|
+
now: /* @__PURE__ */ new Date()
|
|
1441
|
+
});
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (!decision.workItemId) continue;
|
|
1446
|
+
const itemKey = `${decision.orgId}\0${decision.factoryProjectId}\0${decision.workItemId}`;
|
|
1447
|
+
if (inspectedWorkItems.has(itemKey)) continue;
|
|
1448
|
+
inspectedWorkItems.add(itemKey);
|
|
1449
|
+
const item = await this.get({
|
|
1450
|
+
orgId: decision.orgId,
|
|
1451
|
+
id: decision.workItemId
|
|
1452
|
+
});
|
|
1453
|
+
if (!item || !isTerminalFactoryRuleStage(item.stages)) continue;
|
|
1454
|
+
await this.supersedeDecisionsForWorkItem({
|
|
1455
|
+
orgId: decision.orgId,
|
|
1456
|
+
factoryProjectId: decision.factoryProjectId,
|
|
1457
|
+
workItemId: decision.workItemId,
|
|
1458
|
+
supersededAt: /* @__PURE__ */ new Date()
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
const last = page.at(-1);
|
|
1462
|
+
if (rows.length <= ATTENTION_RECEIPT_QUERY_BATCH_SIZE || !last) return;
|
|
1463
|
+
if (!(last.created_at instanceof Date)) throw new Error("Deferred decision created_at is not a timestamp.");
|
|
1464
|
+
cursor = { values: [last.created_at, last.id] };
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1052
1467
|
/** Requeue the same idempotent terminal effect; non-failed decisions are never rerun. */
|
|
1053
1468
|
async retryDeferredDecision(orgId, factoryProjectId, decisionId, now) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1469
|
+
return this.storage.withTransaction(async (ops) => {
|
|
1470
|
+
let retried = false;
|
|
1471
|
+
const row = await ops.updateAtomic("factory_deferred_decisions", {
|
|
1472
|
+
id: decisionId,
|
|
1473
|
+
org_id: orgId,
|
|
1474
|
+
factory_project_id: factoryProjectId
|
|
1475
|
+
}, (current) => {
|
|
1476
|
+
if (current.status !== "failed") return null;
|
|
1477
|
+
retried = true;
|
|
1478
|
+
return {
|
|
1479
|
+
status: "retry",
|
|
1480
|
+
attempts: 0,
|
|
1481
|
+
delivery_generation: Number(current.delivery_generation ?? 0) + 1,
|
|
1482
|
+
available_at: now,
|
|
1483
|
+
lease_owner: null,
|
|
1484
|
+
lease_expires_at: null,
|
|
1485
|
+
last_error: null,
|
|
1486
|
+
failure_code: null,
|
|
1487
|
+
completed_at: null,
|
|
1488
|
+
updated_at: now
|
|
1489
|
+
};
|
|
1490
|
+
});
|
|
1491
|
+
if (!retried || !row) return null;
|
|
1492
|
+
await ops.deleteMany("factory_attention_receipts", {
|
|
1493
|
+
org_id: orgId,
|
|
1494
|
+
factory_project_id: factoryProjectId,
|
|
1495
|
+
kind: "automation-failed",
|
|
1496
|
+
source_id: decisionId,
|
|
1497
|
+
occurrence: Number(row.failure_occurrence ?? 0)
|
|
1498
|
+
});
|
|
1499
|
+
return toDeferredDecision(row);
|
|
1072
1500
|
});
|
|
1073
|
-
return retried && row ? toDeferredDecision(row) : null;
|
|
1074
1501
|
}
|
|
1075
1502
|
/** Resolve exact active agent authority; partial session matches never authorize. */
|
|
1076
1503
|
async findActiveRunBinding(address) {
|
|
@@ -1256,12 +1683,10 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
1256
1683
|
let item;
|
|
1257
1684
|
if (row) {
|
|
1258
1685
|
row = await ops.updateAtomic("work_items", { id: row.id }, (current) => {
|
|
1259
|
-
const roles = /* @__PURE__ */ new Set([...Object.keys(current.sessions), input.role]);
|
|
1260
|
-
const sessions = Object.fromEntries([...roles].map((role) => [role, input.session]));
|
|
1261
1686
|
return applyUpdate({
|
|
1262
1687
|
current,
|
|
1263
1688
|
userId: input.userId,
|
|
1264
|
-
input: { sessions }
|
|
1689
|
+
input: { sessions: { [input.role]: input.session } }
|
|
1265
1690
|
});
|
|
1266
1691
|
});
|
|
1267
1692
|
item = toRow(row);
|
|
@@ -1461,6 +1886,28 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
1461
1886
|
previous: emptyPrior()
|
|
1462
1887
|
};
|
|
1463
1888
|
}
|
|
1889
|
+
async setParentWorkItemIfMissing({ orgId, id, userId, parentWorkItemId }) {
|
|
1890
|
+
const candidate = await this.#db.findOne("work_items", {
|
|
1891
|
+
org_id: orgId,
|
|
1892
|
+
id
|
|
1893
|
+
});
|
|
1894
|
+
if (!candidate) return null;
|
|
1895
|
+
return this.#withProjectRelationTransaction(orgId, candidate.factory_project_id, async (ops) => {
|
|
1896
|
+
const row = await ops.updateAtomic("work_items", {
|
|
1897
|
+
org_id: orgId,
|
|
1898
|
+
id
|
|
1899
|
+
}, async (current) => {
|
|
1900
|
+
if (current.parent_work_item_id !== null) return current;
|
|
1901
|
+
validateParentRelation(await this.#listWithOps(ops, orgId, current.factory_project_id), current.id, parentWorkItemId);
|
|
1902
|
+
return applyUpdate({
|
|
1903
|
+
current,
|
|
1904
|
+
userId,
|
|
1905
|
+
input: { parentWorkItemId }
|
|
1906
|
+
});
|
|
1907
|
+
});
|
|
1908
|
+
return row ? toWorkItem(row) : null;
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1464
1911
|
async update({ orgId, id, userId, patch }) {
|
|
1465
1912
|
const run = async (ops) => {
|
|
1466
1913
|
let previous = emptyPrior();
|
|
@@ -1518,6 +1965,13 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
1518
1965
|
const evaluation = await ops.findOne("factory_rule_evaluations", { id: evaluationId });
|
|
1519
1966
|
if (evaluation) ingressIds.add(String(evaluation.ingress_id));
|
|
1520
1967
|
}
|
|
1968
|
+
const decisionIds = decisions.map((decision) => decision.id);
|
|
1969
|
+
for (let index = 0; index < decisionIds.length; index += ATTENTION_RECEIPT_QUERY_BATCH_SIZE) await ops.deleteMany("factory_attention_receipts", {
|
|
1970
|
+
org_id: orgId,
|
|
1971
|
+
factory_project_id: factoryProjectId,
|
|
1972
|
+
kind: "automation-failed",
|
|
1973
|
+
source_id: { in: decisionIds.slice(index, index + ATTENTION_RECEIPT_QUERY_BATCH_SIZE) }
|
|
1974
|
+
});
|
|
1521
1975
|
await ops.deleteMany("factory_deferred_decisions", {
|
|
1522
1976
|
org_id: orgId,
|
|
1523
1977
|
factory_project_id: factoryProjectId,
|
|
@@ -1559,6 +2013,6 @@ var WorkItemsStorage = class extends FactoryStorageDomain {
|
|
|
1559
2013
|
}
|
|
1560
2014
|
};
|
|
1561
2015
|
//#endregion
|
|
1562
|
-
export { FACTORY_RULE_MATERIALIZATION_KEY, WORK_ITEMS_SCHEMA, WorkItemRelationError, WorkItemsStorage, applyStageTransition, factoryDecisionHash, isAgentActor, stampSessions, validateParentRelation };
|
|
2016
|
+
export { FACTORY_PULL_REQUEST_RECONCILIATION_KEY, FACTORY_RULE_MATERIALIZATION_KEY, WORK_ITEMS_SCHEMA, WorkItemRelationError, WorkItemsStorage, applyStageTransition, factoryAttentionKey, factoryDecisionAttentionIdentity, factoryDecisionHash, isAgentActor, stampSessions, validateParentRelation };
|
|
1563
2017
|
|
|
1564
2018
|
//# sourceMappingURL=base.js.map
|