/** * Factory work-items storage domain. * * Work items belong to a first-class Factory project. External intake items use * a provider-neutral source reference; manual work items have no source. * Stage history is server-owned, while session and metadata patches merge * atomically so concurrent actors do not overwrite each other. The authoritative * Factory transition path keeps one exclusive current stage per item. */ import { FactoryStorageDomain } from '@mastra/core/storage'; import type { CollectionSchema } from '@mastra/core/storage'; import type { FactoryTriageType } from '../../../rules/types.js'; import type { FactoryHealthFinding } from '../../../supervisor/health.js'; export type WorkItemStage = string; export declare function factoryDecisionHash(decision: Record): string; export interface ExternalWorkItemSource { integrationId: string; type: string; /** * The tenant on the platform, never ours: a Slack team (`T0ABC…`), a Discord * guild. Scopes the key, because a platform id such as a channel or a message * `ts` is only unique inside the workspace that issued it. */ workspaceId?: string; externalId: string; url?: string; } /** Dispatcher upsert idempotency token — server bookkeeping, dropped from the read wire. */ export declare const FACTORY_RULE_MATERIALIZATION_KEY = "factoryRuleMaterializationKey"; export declare const FACTORY_PULL_REQUEST_RECONCILIATION_KEY = "factoryPullRequestReconciliation"; export interface WorkItemStageEntry { stage: WorkItemStage; enteredAt: string; exitedAt?: string; by: string; /** * Actor that closed this entry; absent on entries written before exit * stamping existed — treat as human. */ exitedBy?: string; } /** * Whether an actor id marks a move an agent run performed: the binding id the * transition tool stamps (`agent:*`), or the rule that fires off a bound run's * tool result (see `factory/rules/processor.ts`). * * Deliberately narrower than "not a human": the poller stamps * `factory-rule-dispatcher` / `github:*` on every card it syncs from the * upstream repo, so counting those as machine work reports the repo's activity * as the Factory's and pins any such ratio near 100%. */ export declare function isAgentActor(by: string | undefined): boolean; export interface WorkItemSessionRef { sessionId: string; branch: string; threadId: string; startedBy: string; } export interface FactoryRuleIngressRecord { id: string; orgId: string; factoryProjectId: string; identity: string; triggerType: string; transitionId: string; result: Record; createdAt: Date; } export interface CommitFactoryRuleEvaluationInput { orgId: string; factoryProjectId: string; workItemId: string | null; ingress: { identity: string; triggerType: string; }; configVersion: string; expectedRevision: number | null; actor: Record | null; outcome: { status: 'accepted' | 'rejected'; code?: string; reason?: string; }; decisions: Record[]; causalChain: Array<{ ingressId: string; decisionType: string; }>; now: Date; } export type CommitFactoryRuleEvaluationResult = { status: 'committed'; result: Record; } | { status: 'replayed'; result: Record; } | { status: 'missing'; }; export interface FactoryToolResultCursorRecord { bindingId: string; orgId: string; factoryProjectId: string; lastMessageId: string; lastMessageCreatedAt: Date; updatedAt: Date; } export interface FactoryRuleEvaluationRecord { id: string; ingressId: string; workItemId: string | null; configVersion: string; expectedRevision: number | null; outcome: 'accepted' | 'rejected'; code: string | null; reason: string | null; causalChain: Array<{ ingressId: string; decisionType: string; }>; createdAt: Date; } /** `proposed` is parked awaiting approval; `dismissed` is human, `superseded` is automatic. */ export type FactoryDispatchStatus = 'pending' | 'proposed' | 'dismissed' | 'superseded' | 'leased' | 'retry' | 'succeeded' | 'failed'; declare const FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown"]; declare const STORED_FACTORY_DISPATCH_FAILURE_CODES: readonly ["session_unavailable", "source_control_missing", "source_repository_missing", "unsupported_provider_item", "notification_delivery_failed", "run_overdue", "repository_git_missing", "repository_egress_blocked", "repository_clone_failed", "repository_pull_failed", "repository_push_failed", "repository_commit_failed", "repository_cli_missing", "repository_pr_failed", "run_configuration_invalid", "unknown", "plan_awaiting_approval", "run_awaiting_input"]; export type FactoryDispatchFailureCode = (typeof FACTORY_DISPATCH_FAILURE_CODES)[number]; export type StoredFactoryDispatchFailureCode = (typeof STORED_FACTORY_DISPATCH_FAILURE_CODES)[number]; export interface FactoryDeferredDecisionPageInput { orgId: string; factoryProjectId: string; statuses?: FactoryDispatchStatus[]; before?: { createdAt: Date; id: string; }; limit: number; } export interface FactoryDeferredDecisionPage { decisions: FactoryDeferredDecisionRecord[]; hasMore: boolean; } export interface FactoryDecisionStatusPageInput { orgId: string; factoryProjectId: string; status: FactoryDispatchStatus; before?: { occurredAt: Date; id: string; }; limit: number; } export interface FactoryDeferredDecisionRecord { id: string; orgId: string; factoryProjectId: string; evaluationId: string; workItemId: string | null; idempotencyKey: string; effectOrdinal: number; effectHash: string; causalChain: Array<{ ingressId: string; decisionType: string; }>; actor: Record | null; decision: Record; status: FactoryDispatchStatus; attempts: number; deliveryGeneration: number; failureOccurrence: number; availableAt: Date; leaseOwner: string | null; leaseExpiresAt: Date | null; lastError: string | null; failureCode: StoredFactoryDispatchFailureCode | null; /** When a human released this run; set once, so the gate never parks it again. */ approvedAt: Date | null; /** Who released this run — the run is attributed to them, not the repo connector. */ approvedBy: string | null; completedAt: Date | null; createdAt: Date; updatedAt: Date; } export type FactoryAttentionKind = 'automation-failed' | 'automation-proposed' | 'mention' | 'activity' | 'supervisor-finding' | 'agent-waiting'; export type FactoryAttentionReceiptState = 'read' | 'archived'; export interface FactorySupervisorFindingRecord { id: string; orgId: string; factoryProjectId: string; findingKey: string; occurrence: number; finding: Record; openedAt: Date; updatedAt: Date; resolvedAt: Date | null; } export type FactoryAttentionReceiptAction = 'read' | 'archive' | 'restore'; export interface FactoryAttentionIdentity { kind: FactoryAttentionKind; sourceId: string; occurrence: number; } export interface FactoryAttentionReceiptRecord extends FactoryAttentionIdentity { id: string; orgId: string; factoryProjectId: string; userId: string; state: FactoryAttentionReceiptState; readAt: Date; archivedAt: Date | null; createdAt: Date; updatedAt: Date; } interface SetAttentionReceiptInput { orgId: string; factoryProjectId: string; userId: string; identity: FactoryAttentionIdentity; action: FactoryAttentionReceiptAction; now: Date; } export declare function factoryDecisionAttentionIdentity(decisionId: string, failureOccurrence: number): FactoryAttentionIdentity; export declare function factoryProposalAttentionIdentity(decisionId: string): FactoryAttentionIdentity; export declare function factoryMentionAttentionIdentity(commentId: string): FactoryAttentionIdentity; /** Collapsed per work item, so the occurrence is what a new comment bumps. */ export declare function factoryActivityAttentionIdentity(workItemId: string, occurrence: number): FactoryAttentionIdentity; export declare function factorySupervisorFindingAttentionIdentity(findingKey: string, occurrence: number): FactoryAttentionIdentity; /** Dated by the park itself, so an answer and a new park never share a receipt. */ export declare function factoryAgentWaitingAttentionIdentity(sessionId: string, suspendedAt: number): FactoryAttentionIdentity; export declare function factoryAttentionKey(factoryProjectId: string, identity: FactoryAttentionIdentity): string; export interface FactoryRunBindingSessionAddress { factoryProjectId: string; threadId: string; resourceId: string; sessionId: string; } export interface FactoryRunBindingAddress extends FactoryRunBindingSessionAddress { orgId: string; } export interface RevokeFactoryRunBindingInput { orgId: string; factoryProjectId: string; bindingId: string; revokedAt: Date; } export interface RevokeStaleFactoryRunBindingsInput { /** Active bindings created before this instant are revoked regardless of item state. */ olderThan: Date; now: Date; } export interface RevokeFactoryRunBindingsForWorkItemInput { orgId: string; factoryProjectId: string; workItemId: string; revokedAt: Date; } export interface FactoryRunBindingRecord { id: string; orgId: string; factoryProjectId: string; workItemId: string; role: string; threadId: string; resourceId: string; sessionId: string; branch: string; status: 'active' | 'revoked'; createdAt: Date; revokedAt: Date | null; } export interface FactoryPendingStartRecord { id: string; orgId: string; factoryProjectId: string; bindingId: string; kickoffKey: string; message: string | null; status: 'pending' | 'leased' | 'retry' | 'sent' | 'failed'; attempts: number; availableAt: Date; leaseOwner: string | null; leaseExpiresAt: Date | null; lastError: string | null; failureCode: StoredFactoryDispatchFailureCode | null; completedAt: Date | null; createdAt: Date; updatedAt: Date; } export interface FactoryLeaseClaimInput { ownerId: string; now: Date; leaseExpiresAt: Date; limit: number; } export interface FactoryLeaseIdentity { id: string; orgId: string; factoryProjectId: string; ownerId: string; } export interface FactoryDispatchFailureInput extends FactoryLeaseIdentity { now: Date; availableAt: Date; lastError: string; failureCode: FactoryDispatchFailureCode; terminal: boolean; advanceDeliveryGeneration?: boolean; } export interface CommitFactoryTransitionInput { orgId: string; factoryProjectId: string; workItemId: string; expectedRevision: number; destinationStage: string; actorId: string; ingress: { identity: string; triggerType: string; transitionId: string; }; configVersion: string; causalChain: Array<{ ingressId: string; decisionType: string; }>; evaluation: { outcome: 'accepted'; decisions: Record[]; } | { outcome: 'rejected'; code: string; reason: string; }; /** Arm or disarm autonomy in the same revision-checked update that commits the transition. */ autonomy?: 'arm' | 'disarm'; /** Consent-bearing actor behind the flip; their id pre-approves the runs this transition queues. */ consentedBy?: string; /** Triage classification reported by an authenticated triage binding. */ triageType?: FactoryTriageType; /** Record the person's acceptance of this item in the same revision-checked update; a no-op once set. */ accept?: boolean; } export type CommitFactoryTransitionResult = { status: 'committed'; item: WorkItemRow | null; result: Record; } | { status: 'replayed'; item: WorkItemRow | null; result: Record; } | { status: 'missing'; }; export interface PrepareFactoryRunStartInput { orgId: string; userId: string; factoryProjectId: string; workItem: { id?: string; input: CreateWorkItemInput; }; role: string; session: WorkItemSessionInput; resourceId: string; kickoffKey: string; kickoffMessage: string | null; } export interface PrepareFactoryRunStartResult { item: WorkItemRow; binding: FactoryRunBindingRecord; pendingStart: FactoryPendingStartRecord; replayed: boolean; } /** Session ref as accepted from clients — `startedBy` is stamped server-side. */ export interface WorkItemSessionInput { sessionId: string; branch: string; threadId: string; } export type WorkItemSessions = Record; export interface WorkItemRow { id: string; orgId: string; factoryProjectId: string; board: string | null; externalSource: ExternalWorkItemSource | null; parentWorkItemId: string | null; title: string; stages: WorkItemStage[]; stageHistory: WorkItemStageEntry[]; sessions: WorkItemSessions; metadata: Record | null; /** Authoritative verdict written by the bound triage run. */ triageType: FactoryTriageType | null; /** * When a person first committed this item to the Factory, by starting a run * on it or releasing one that was proposed. Projects that withhold auto-run * are asking to decide what the Factory picks up, not to approve each step of * work they already asked for, so runs on an armed item skip the gate. */ autonomyArmedAt: Date | null; /** * When a person chose to run this item hands-off: the dispatcher answers its * parked plans even while the project's Auto-approve plans switch is off. */ plansPreapprovedAt: Date | null; /** * When a person first moved this item out of Intake/Triage into working * stages. Non-bug items wait for that gesture; once it is recorded the * agents may advance the item through Planning and Execute on their own. */ acceptedAt: Date | null; /** Denormalized feed counters, maintained by the comments domain via recount. */ commentCount: number; /** Bumps on every feed mutation (create/edit/delete) — the clients' change hint. */ feedActivityAt: Date | null; revision: number; createdBy: string; createdAt: Date; updatedAt: Date; } export interface CreateWorkItemInput { board?: string; externalSource?: ExternalWorkItemSource | null; parentWorkItemId?: string | null; title: string; stages?: WorkItemStage[]; sessions?: Record; metadata?: Record | null; } export interface UpdateWorkItemInput { board?: string; parentWorkItemId?: string | null; title?: string; stages?: WorkItemStage[]; sessions?: Record; metadata?: Record | null; /** The person chose a hands-off run: stamped once, so a second grant never moves it. */ plansPreapproved?: true; } export interface WorkItemPriorState { stages: WorkItemStage[]; sessionRoles: string[]; } export interface UpsertWorkItemResult { item: WorkItemRow; created: boolean; previous: WorkItemPriorState; } export declare const WORK_ITEMS_SCHEMA: CollectionSchema; /** * The one shape a platform id takes in a lookup key, for cards and for the * comments mirrored off them alike: whoever writes a second builder reopens the * cross-workspace collision the `workspaceId` is here to close. */ export declare function externalSourceKey(source: ExternalWorkItemSource | null | undefined): string | null; export declare class WorkItemRelationError extends Error { readonly code = "invalid_work_item_relation"; } export declare class WorkItemUpdateConflictError extends Error { readonly reason: 'board' | 'revision'; readonly code = "work_item_update_conflict"; constructor(reason: 'board' | 'revision'); } export declare function validateParentRelation(projectItems: WorkItemRow[], itemId: string | undefined, parentWorkItemId: string | null): void; /** * Diff `oldStages` → `newStages` and return the updated history: exited stages * get `exitedAt` + `exitedBy` stamped on their open entry, entered stages get * a new entry. */ export declare function applyStageTransition(history: WorkItemStageEntry[], oldStages: WorkItemStage[], newStages: WorkItemStage[], by: string, now: Date): WorkItemStageEntry[]; export declare function stampSessions(sessions: Record, by: string): WorkItemSessions; /** The project whose attention list a write just changed. */ export interface FactoryAttentionScope { orgId: string; factoryProjectId: string; } export declare class WorkItemsStorage extends FactoryStorageDomain { #private; constructor(); /** * Wired once at boot. Every write below that changes what this project's * attention list projects announces it here — the one place a new such write * has to remember, since clients stop polling while their stream is up. */ onAttentionChanged(listener: (scope: FactoryAttentionScope) => void): void; /** * Wired once at boot, before `init()`. Tells the legacy attention sweep and * the stale-binding sweep which cards sit in a phase their installed board * declares terminal. */ useTerminalPhasePredicate(isTerminal: (item: WorkItemRow) => boolean): void; init(): Promise; dangerouslyClearAll(): Promise; syncSupervisorFindings(input: { orgId: string; factoryProjectId: string; findings: FactoryHealthFinding[]; now: Date; }): Promise; listSupervisorFindingPage(input: { orgId: string; factoryProjectId: string; before?: { occurredAt: Date; id: string; }; limit: number; }): Promise<{ rows: FactorySupervisorFindingRecord[]; hasMore: boolean; }>; countOpenSupervisorFindings(input: { orgId: string; factoryProjectId: string; }): Promise; /** * List the org's work items for a project, newest first. Ordered on `created_at` with an id * tiebreak so the order is stable: `updated_at` moves under every write, which makes it useless * as a cursor and non-deterministic for the callers that iterate this list. */ list({ orgId, factoryProjectId }: { orgId: string; factoryProjectId: string; }): Promise; listByIds({ orgId, factoryProjectId, ids, }: { orgId: string; factoryProjectId: string; ids: string[]; }): Promise; /** * Strip every ref to a retired session; matching happens in app code because * `findMany` cannot reach inside the `sessions` JSON column. * ponytail: org-wide scan per session delete; JSON-path query if it measures. */ clearSessionReferences({ orgId, sessionId }: { orgId: string; sessionId: string; }): Promise; getByProjectSource({ orgId, factoryProjectId, source, }: { orgId: string; factoryProjectId: string; source: ExternalWorkItemSource; }): Promise; get({ orgId, id }: { orgId: string; id: string; }): Promise; /** * Resolve the card a platform thread created, given only its external source. * Bare of org/project because an inbound platform message carries neither: an * unlinked sender has no tenant. Tenant safety rides on the key's * `workspaceId` instead. Ambiguous matches resolve to nothing, not a guess. */ getBySource(source: ExternalWorkItemSource): Promise; getForProject(orgId: string, factoryProjectId: string, id: string): Promise; getTransitionResultByIngress(orgId: string, factoryProjectId: string, identity: string): Promise | null>; commitTransition(input: CommitFactoryTransitionInput): Promise; commitRuleEvaluation(input: CommitFactoryRuleEvaluationInput): Promise; getToolResultCursor(orgId: string, factoryProjectId: string, bindingId: string): Promise; advanceToolResultCursor(cursor: FactoryToolResultCursorRecord): Promise; listDeferredDecisions(orgId: string, factoryProjectId: string): Promise; /** Read a bounded newest-first status page without exposing another tenant. */ listDeferredDecisionPage(input: FactoryDeferredDecisionPageInput): Promise; /** Newest-parked-first keyset over one status, on the `updated_at` the status was stamped. */ listDecisionPageByStatus(input: FactoryDecisionStatusPageInput): Promise; countDeferredDecisionsByStatuses({ orgId, factoryProjectId, statuses, }: { orgId: string; factoryProjectId: string; statuses: FactoryDispatchStatus[]; }): Promise; getDeferredDecision(orgId: string, factoryProjectId: string, decisionId: string): Promise; listAttentionReceipts({ orgId, factoryProjectId, userId, identities, }: { orgId: string; factoryProjectId: string; userId: string; identities: FactoryAttentionIdentity[]; }): Promise; countAttentionReceipts({ orgId, factoryProjectId, userId, kind, state, }: { orgId: string; factoryProjectId: string; userId: string; kind: FactoryAttentionKind; state?: FactoryAttentionReceiptState; }): Promise; deleteAttentionReceipts({ orgId, factoryProjectId, userId, identities, }: { orgId: string; factoryProjectId: string; /** Restrict to one user's receipts (a removed mention must keep other users' read state). */ userId?: string; identities: FactoryAttentionIdentity[]; }): Promise; setAttentionReceipt(input: SetAttentionReceiptInput): Promise; markAttentionReceiptsRead({ orgId, factoryProjectId, userId, identities, now, }: { orgId: string; factoryProjectId: string; userId: string; identities: FactoryAttentionIdentity[]; now: Date; }): Promise; claimDeferredDecisions(input: FactoryLeaseClaimInput): Promise; renewDeferredDecisionLease(identity: FactoryLeaseIdentity, leaseExpiresAt: Date): Promise; completeDeferredDecision(identity: FactoryLeaseIdentity, now: Date): Promise; failDeferredDecision(input: FactoryDispatchFailureInput): Promise; /** Park a claimed effect for human approval; the dispatcher never claims `proposed` rows. */ proposeDeferredDecision(identity: FactoryLeaseIdentity, now: Date): Promise; /** * Release an approved effect back to the dispatcher; only `proposed` rows are * approvable. Approval is a person taking the item on, so the item's autonomy * is armed in the same transaction — a crash cannot release the run while * leaving its follow-up work parked for re-approval. */ approveDeferredDecision(orgId: string, factoryProjectId: string, decisionId: string, now: Date, approvedBy?: string): Promise; /** Retire a proposal nobody wants: `dismissed` is terminal, so the run never happens. */ dismissDeferredDecision(orgId: string, factoryProjectId: string, decisionId: string, now: Date): Promise; /** * Automatically retire proposed or failed work that a newer run or terminal * work-item state has overtaken. Human dismissal remains `dismissed`. */ supersedeDecisionsForWorkItem(input: { orgId: string; factoryProjectId: string; workItemId: string; role?: string; supersededAt: Date; }): Promise; supersedeTerminalDecisionsForWorkItem(input: { orgId: string; factoryProjectId: string; workItemId: string; supersededAt: Date; }): Promise; repairLegacyAttentionState(): Promise; /** Requeue the same idempotent terminal effect; non-failed decisions are never rerun. */ retryDeferredDecision(orgId: string, factoryProjectId: string, decisionId: string, now: Date): Promise; /** Resolve exact active agent authority; partial session matches never authorize. */ findActiveRunBinding(address: FactoryRunBindingAddress): Promise; /** * Recover the active binding for a thread when session state lost * `factoryProjectId` (e.g. crash-resume recreated the session empty). * Ambiguous matches across factory projects never authorize. */ findActiveRunBindingByThread(input: { orgId: string; threadId: string; resourceId: string; sessionId: string; }): Promise; /** Resolve exact bound-session state for processor awareness; ambiguous cross-tenant matches return null. */ findRunBindingBySession(address: FactoryRunBindingSessionAddress): Promise; /** Revoke one exact tenant-scoped binding. */ revokeRunBinding(input: RevokeFactoryRunBindingInput): Promise; /** * Revoke every active binding for one work item (all roles). Called from * terminal-stage cleanup so completed items stop paying the reconcile walk. * Returns the number of bindings revoked. */ revokeRunBindingsForWorkItem(input: RevokeFactoryRunBindingsForWorkItemInput): Promise; /** * Revoke leaked/legacy active bindings: older than `olderThan`, or whose * work item is gone, malformed, or already terminal. Terminal-stage cleanup * handles bindings go-forward; this sweep drains anything that slipped past * it. Returns the number of bindings revoked. */ revokeStaleRunBindings(input: RevokeStaleFactoryRunBindingsInput): Promise; /** Enumerate active bindings for the server-owned restart reconciler. */ listActiveRunBindings(): Promise; /** List binding history, optionally narrowed to one work item. */ listRunBindings(orgId: string, factoryProjectId: string, workItemId?: string): Promise; listPendingStarts(orgId: string, factoryProjectId: string): Promise; claimPendingStarts(input: FactoryLeaseClaimInput): Promise; renewPendingStartLease(identity: FactoryLeaseIdentity, leaseExpiresAt: Date): Promise; completePendingStart(identity: FactoryLeaseIdentity, now: Date): Promise; failPendingStart(input: FactoryDispatchFailureInput): Promise; prepareRunStart(input: PrepareFactoryRunStartInput): Promise; markPendingStart(bindingId: string, status: 'sent' | 'failed', lastError?: string): Promise; /** * Create a work item, reusing the existing record when `sourceKey` already * has one for the project (acting twice on the same issue must not duplicate * the card). On reuse the provided stages replace the current ones (with the * transition recorded in history) and sessions/metadata are merged in. The * result discriminates insert from reuse so callers can audit the actual * outcome. */ upsert(params: { orgId: string; userId: string; factoryProjectId: string; input: CreateWorkItemInput; reuseMode?: 'update' | 'preserve' | 'non-stage'; }): Promise; setParentWorkItemIfMissing({ orgId, id, userId, parentWorkItemId, }: { orgId: string; id: string; userId: string; parentWorkItemId: string; }): Promise; update({ orgId, id, userId, patch, expectedRevision, expectedBoard, }: { orgId: string; id: string; userId: string; patch: UpdateWorkItemInput; expectedRevision?: number; expectedBoard?: string | null; }): Promise<{ item: WorkItemRow; previous: WorkItemPriorState; } | null>; delete({ orgId, id }: { orgId: string; id: string; }): Promise; } export {}; //# sourceMappingURL=base.d.ts.map