@mastra/factory 0.12.0-alpha.0 → 0.12.0-alpha.2

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/factory.d.ts.map +1 -1
  3. package/dist/factory.js +6 -1
  4. package/dist/factory.js.map +1 -1
  5. package/dist/feed-events.d.ts +13 -0
  6. package/dist/feed-events.d.ts.map +1 -0
  7. package/dist/feed-events.js +25 -0
  8. package/dist/feed-events.js.map +1 -0
  9. package/dist/integrations/github/issue-reconciler.d.ts.map +1 -1
  10. package/dist/integrations/github/issue-reconciler.js +22 -14
  11. package/dist/integrations/github/issue-reconciler.js.map +1 -1
  12. package/dist/integrations/github/reconcile-worker.js +1 -1
  13. package/dist/integrations/github/rules.d.ts +6 -0
  14. package/dist/integrations/github/rules.d.ts.map +1 -1
  15. package/dist/integrations/github/rules.js +58 -12
  16. package/dist/integrations/github/rules.js.map +1 -1
  17. package/dist/integrations/issue-reconcile-worker.js +1 -1
  18. package/dist/integrations/platform/github/event-worker.js +1 -1
  19. package/dist/integrations/platform/linear/event-worker.js +1 -1
  20. package/dist/routes/attention-activity.d.ts +24 -0
  21. package/dist/routes/attention-activity.d.ts.map +1 -0
  22. package/dist/routes/attention-activity.js +143 -0
  23. package/dist/routes/attention-activity.js.map +1 -0
  24. package/dist/routes/attention-providers.d.ts +31 -1
  25. package/dist/routes/attention-providers.d.ts.map +1 -1
  26. package/dist/routes/attention-providers.js +4 -4
  27. package/dist/routes/attention-providers.js.map +1 -1
  28. package/dist/routes/attention.d.ts.map +1 -1
  29. package/dist/routes/attention.js +39 -11
  30. package/dist/routes/attention.js.map +1 -1
  31. package/dist/routes/surface.d.ts.map +1 -1
  32. package/dist/routes/surface.js +4 -3
  33. package/dist/routes/surface.js.map +1 -1
  34. package/dist/routes/work-items.d.ts.map +1 -1
  35. package/dist/routes/work-items.js +11 -1
  36. package/dist/routes/work-items.js.map +1 -1
  37. package/dist/rules/defaults.d.ts.map +1 -1
  38. package/dist/rules/defaults.js +20 -8
  39. package/dist/rules/defaults.js.map +1 -1
  40. package/dist/rules/dispatcher.d.ts.map +1 -1
  41. package/dist/rules/dispatcher.js +24 -10
  42. package/dist/rules/dispatcher.js.map +1 -1
  43. package/dist/rules/processor.d.ts.map +1 -1
  44. package/dist/rules/processor.js +2 -3
  45. package/dist/rules/processor.js.map +1 -1
  46. package/dist/rules/transition-service.d.ts +2 -2
  47. package/dist/rules/transition-service.d.ts.map +1 -1
  48. package/dist/rules/transition-service.js +33 -12
  49. package/dist/rules/transition-service.js.map +1 -1
  50. package/dist/rules/types.d.ts +27 -1
  51. package/dist/rules/types.d.ts.map +1 -1
  52. package/dist/rules/types.js +37 -1
  53. package/dist/rules/types.js.map +1 -1
  54. package/dist/rules/validation.d.ts.map +1 -1
  55. package/dist/rules/validation.js +3 -1
  56. package/dist/rules/validation.js.map +1 -1
  57. package/dist/storage/domains/comments/activity.d.ts +40 -0
  58. package/dist/storage/domains/comments/activity.d.ts.map +1 -0
  59. package/dist/storage/domains/comments/activity.js +94 -0
  60. package/dist/storage/domains/comments/activity.js.map +1 -0
  61. package/dist/storage/domains/comments/base.d.ts +13 -1
  62. package/dist/storage/domains/comments/base.d.ts.map +1 -1
  63. package/dist/storage/domains/comments/base.js +41 -3
  64. package/dist/storage/domains/comments/base.js.map +1 -1
  65. package/dist/storage/domains/comments/domain.d.ts +4 -1
  66. package/dist/storage/domains/comments/domain.d.ts.map +1 -1
  67. package/dist/storage/domains/comments/domain.js +17 -5
  68. package/dist/storage/domains/comments/domain.js.map +1 -1
  69. package/dist/storage/domains/comments/parse.d.ts +1 -0
  70. package/dist/storage/domains/comments/parse.d.ts.map +1 -1
  71. package/dist/storage/domains/comments/parse.js +2 -2
  72. package/dist/storage/domains/comments/parse.js.map +1 -1
  73. package/dist/storage/domains/comments/routes.d.ts +2 -0
  74. package/dist/storage/domains/comments/routes.d.ts.map +1 -1
  75. package/dist/storage/domains/comments/routes.js +49 -3
  76. package/dist/storage/domains/comments/routes.js.map +1 -1
  77. package/dist/storage/domains/comments/schema.d.ts +1 -0
  78. package/dist/storage/domains/comments/schema.d.ts.map +1 -1
  79. package/dist/storage/domains/comments/schema.js +45 -1
  80. package/dist/storage/domains/comments/schema.js.map +1 -1
  81. package/dist/storage/domains/work-items/base.d.ts +18 -3
  82. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  83. package/dist/storage/domains/work-items/base.js +95 -38
  84. package/dist/storage/domains/work-items/base.js.map +1 -1
  85. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../src/rules/types.ts"],"sourcesContent":["export type WorkItemSource = 'github-issue' | 'github-pr' | 'linear-issue' | 'manual';\n\nexport const FACTORY_RULE_STAGES = ['intake', 'triage', 'planning', 'execute', 'review', 'done', 'canceled'] as const;\nexport type FactoryRuleStage = (typeof FACTORY_RULE_STAGES)[number];\n\nexport const FACTORY_TRIAGE_TYPES = [\n 'bug',\n 'feature request',\n 'docs',\n 'question/support',\n 'maintenance',\n 'duplicate',\n 'resolved',\n 'invalid',\n 'spam',\n 'out-of-scope',\n 'other',\n] as const;\nexport type FactoryTriageType = (typeof FACTORY_TRIAGE_TYPES)[number];\n\nexport function isFactoryTriageType(value: unknown): value is FactoryTriageType {\n return typeof value === 'string' && FACTORY_TRIAGE_TYPES.some(type => type === value);\n}\n\nexport function isFactoryRuleStage(value: unknown): value is FactoryRuleStage {\n return typeof value === 'string' && FACTORY_RULE_STAGES.some(stage => stage === value);\n}\n\nexport function factoryRuleStage(stages: readonly string[]): FactoryRuleStage | undefined {\n const stage = stages.length === 1 ? stages[0] : undefined;\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function isTerminalFactoryRuleStage(stages: readonly string[]): boolean {\n const stage = factoryRuleStage(stages);\n return stage === 'done' || stage === 'canceled';\n}\n\nexport const FACTORY_RULE_BOARDS = ['work', 'review'] as const;\nexport type FactoryRuleBoard = (typeof FACTORY_RULE_BOARDS)[number];\n\nexport const FACTORY_RULE_SOURCES = ['issue', 'pullRequest', 'linearIssue', 'manual'] as const;\nexport type FactoryRuleSource = (typeof FACTORY_RULE_SOURCES)[number];\n\nexport const FACTORY_GITHUB_EVENTS = [\n 'issueOpened',\n 'issueEdited',\n 'issueClosed',\n 'issueCommentCreated',\n 'issueCommentEdited',\n 'issueCommentDeleted',\n 'pullRequestOpened',\n 'pullRequestUpdated',\n 'pullRequestCommentCreated',\n 'pullRequestReviewRequested',\n 'pullRequestReviewSubmitted',\n 'pullRequestMerged',\n 'pullRequestClosed',\n] as const;\nexport type FactoryGithubEventName = (typeof FACTORY_GITHUB_EVENTS)[number];\n\nexport const FACTORY_LINEAR_EVENTS = ['issueObserved', 'issueClosed'] as const;\nexport type FactoryLinearEventName = (typeof FACTORY_LINEAR_EVENTS)[number];\n\nexport type FactoryRuleJsonValue =\n | null\n | boolean\n | number\n | string\n | FactoryRuleJsonValue[]\n | { [key: string]: FactoryRuleJsonValue };\n\nexport interface FactoryRuleItemContext {\n id: string;\n source: WorkItemSource;\n sourceKey: string | null;\n parentWorkItemId: string | null;\n title: string;\n url: string | null;\n stages: readonly string[];\n /** Intake-stamped facts about the source — repository id, reporter login, labels. */\n metadata: Record<string, unknown> | null;\n}\n\nexport type FactoryRuleActor =\n | { type: 'human'; id: string }\n | { type: 'agent'; bindingId: string; role: string }\n | { type: 'github'; login: string; trusted: boolean; factoryAuthored: boolean }\n | { type: 'system'; id: string };\n\nexport interface FactoryRuleIngressIdentity {\n type: 'human' | 'agent' | 'toolResult' | 'github' | 'linear' | 'rule';\n id: string;\n}\n\nexport interface FactoryRuleCausalEntry {\n ingressId: string;\n decisionType: FactoryCommitDecision['type'];\n}\n\nexport interface FactoryRuleContextBase {\n tenant: { orgId: string; projectId: string };\n actor: FactoryRuleActor;\n ingress: FactoryRuleIngressIdentity;\n cause: string;\n causalChain: readonly FactoryRuleCausalEntry[];\n ruleSetVersion: string;\n}\n\nexport interface FactoryBoundRuleContext extends FactoryRuleContextBase {\n item: FactoryRuleItemContext;\n board: FactoryRuleBoard;\n itemRevision: number;\n}\n\nexport interface FactoryStageRuleContext extends FactoryBoundRuleContext {\n source: FactoryRuleSource;\n stage: FactoryRuleStage;\n fromStage: FactoryRuleStage;\n toStage: FactoryRuleStage;\n}\n\nexport interface FactoryToolResultRuleContext extends FactoryBoundRuleContext {\n toolName: string;\n threadId: string;\n assistantMessageId: string;\n toolCallId: string;\n result: {\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n };\n}\n\nexport interface FactoryGithubRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryGithubEventName;\n deliveryId: string;\n factory: { createdAt: string };\n repository: { id: number; fullName: string };\n issue?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n updatedAt?: string;\n assignees?: string[];\n labels?: string[];\n state?: 'open' | 'closed';\n /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */\n stateReason?: string;\n };\n issueChange?: { title: boolean; body: boolean };\n issueComment?: {\n id: number;\n body?: string;\n url?: string;\n author?: string;\n authorType?: string;\n createdAt?: string;\n updatedAt?: string;\n };\n pullRequest?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n state: 'open' | 'closed';\n draft: boolean;\n merged: boolean;\n assignees?: string[];\n requestedReviewers?: string[];\n labels?: string[];\n headBranch: string;\n baseBranch: string;\n };\n /** Present on `pullRequestReviewRequested`: who review was (re-)requested from. */\n reviewRequest?: { reviewer: string; factoryReviewer: boolean };\n /** Present on `pullRequestReviewSubmitted`: the review that was just posted. */\n review?: { id: number; state: string; url: string };\n}\n\nexport interface FactoryLinearRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryLinearEventName;\n issue: {\n id: string;\n identifier: string;\n title: string;\n url: string;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n creator: string | null;\n team: string | null;\n labels: readonly string[];\n createdAt: string;\n updatedAt: string;\n };\n}\n\nexport type FactoryRuleHandler<TContext> = (\n context: Readonly<TContext>,\n) => FactoryRuleDecision | void | Promise<FactoryRuleDecision | void>;\n\nexport interface FactoryBoardRuleLeaf {\n onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;\n onExit?: FactoryRuleHandler<FactoryStageRuleContext>;\n}\n\nexport interface FactoryToolRuleLeaf {\n onResult?: FactoryRuleHandler<FactoryToolResultRuleContext>;\n}\n\nexport interface FactoryGithubRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryGithubRuleContext>;\n}\n\nexport interface FactoryLinearRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryLinearRuleContext>;\n}\n\nexport type FactoryBoardRules = Partial<\n Record<FactoryRuleStage, Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>>>\n>;\n\nexport interface FactoryRules {\n version: string;\n work: FactoryBoardRules;\n review: FactoryBoardRules;\n tools: Record<string, FactoryToolRuleLeaf>;\n github: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;\n linear: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport interface FactoryRulesOverrides {\n work?: FactoryBoardRules;\n review?: FactoryBoardRules;\n tools?: Record<string, FactoryToolRuleLeaf>;\n github?: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;\n linear?: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport type FactoryRuleRejectionCode =\n | 'forbidden'\n | 'invalid_transition'\n | 'missing_binding'\n | 'stale'\n | 'timeout'\n | 'rule_error'\n | 'causal_depth_exceeded'\n | 'repeated_transition'\n | 'approval_required';\n\nexport interface FactoryRuleRejectDecision {\n type: 'reject';\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\ninterface FactoryCommitDecisionBase {\n idempotencyKey: string;\n}\n\nexport interface FactoryTransitionDecision extends FactoryCommitDecisionBase {\n type: 'transition';\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n /**\n * Delivered to the item's active session (waking it if idle) after the\n * transition commits. Skipped when the item has no active run binding, so\n * informational messages never fail the transition.\n */\n message?: { text: string; role?: string };\n /**\n * Runs the stage's entry rules even when the item is already in that stage.\n * A transition to the current stage is normally inert, because most callers\n * are correcting a board into a state it already holds. Re-entry is for the\n * opposite case: the stage's work is in flight and has been invalidated, so\n * it has to start over.\n */\n reenter?: boolean;\n}\n\nexport interface FactoryUpsertLinkedWorkItemDecision extends FactoryCommitDecisionBase {\n type: 'upsertLinkedWorkItem';\n board: FactoryRuleBoard;\n source: WorkItemSource;\n sourceKey: string;\n title: string;\n url: string | null;\n stage: FactoryRuleStage;\n metadata?: Record<string, FactoryRuleJsonValue>;\n}\n\ninterface FactoryInvokeSkillDecisionBase extends FactoryCommitDecisionBase {\n type: 'invokeSkill';\n role: string;\n arguments?: string;\n precedingMessage?: string;\n cancelInFlight?: boolean;\n}\n\n/**\n * Starting an agent run. Most runs activate a skill, because the skill carries\n * the handoff contract later rules match on. A run whose completion is already\n * signalled some other way — Building finishes by opening a pull request, which\n * arrives as its own event — needs no contract, so it can carry a plain prompt\n * instead of an otherwise empty skill.\n */\nexport type FactoryInvokeSkillDecision = FactoryInvokeSkillDecisionBase &\n ({ skillName: string; prompt?: never } | { prompt: string; skillName?: never });\n\nexport interface FactorySendMessageDecision extends FactoryCommitDecisionBase {\n type: 'sendMessage';\n role: string;\n message: string;\n priority?: 'medium' | 'high' | 'urgent';\n idleBehavior?: 'persist' | 'wake';\n prepareBinding?: boolean;\n}\n\nexport interface FactoryNotifyDecision extends FactoryCommitDecisionBase {\n type: 'notify';\n title: string;\n body?: string;\n level?: 'info' | 'warning' | 'error';\n}\n\nexport type FactoryCommitDecision =\n | FactoryTransitionDecision\n | FactoryUpsertLinkedWorkItemDecision\n | FactoryInvokeSkillDecision\n | FactorySendMessageDecision\n | FactoryNotifyDecision;\n\nexport type FactoryRuleDecision = FactoryRuleRejectDecision | FactoryCommitDecision;\n\nexport interface FactoryTransitionResultAccepted {\n status: 'accepted';\n transitionId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n decisions: FactoryCommitDecision[];\n}\n\nexport interface FactoryTransitionResultRejected {\n status: 'rejected';\n transitionId: string;\n itemId: string;\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\nexport type FactoryTransitionResult = FactoryTransitionResultAccepted | FactoryTransitionResultRejected;\n\nexport function factoryRuleSourceForWorkItem(source: WorkItemSource): FactoryRuleSource {\n switch (source) {\n case 'github-issue':\n return 'issue';\n case 'github-pr':\n return 'pullRequest';\n case 'linear-issue':\n return 'linearIssue';\n case 'manual':\n return 'manual';\n }\n}\n"],"mappings":";AAEA,MAAa,sBAAsB;CAAC;CAAU;CAAU;CAAY;CAAW;CAAU;CAAQ;AAAU;AAG3G,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,SAAgB,oBAAoB,OAA4C;CAC9E,OAAO,OAAO,UAAU,YAAY,qBAAqB,MAAK,SAAQ,SAAS,KAAK;AACtF;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,OAAO,UAAU,YAAY,oBAAoB,MAAK,UAAS,UAAU,KAAK;AACvF;AAEA,SAAgB,iBAAiB,QAAyD;CACxF,MAAM,QAAQ,OAAO,WAAW,IAAI,OAAO,KAAK,KAAA;CAChD,OAAO,mBAAmB,KAAK,IAAI,QAAQ,KAAA;AAC7C;AAEA,SAAgB,2BAA2B,QAAoC;CAC7E,MAAM,QAAQ,iBAAiB,MAAM;CACrC,OAAO,UAAU,UAAU,UAAU;AACvC;AAEA,MAAa,sBAAsB,CAAC,QAAQ,QAAQ;AAGpD,MAAa,uBAAuB;CAAC;CAAS;CAAe;CAAe;AAAQ;AAGpF,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,wBAAwB,CAAC,iBAAiB,aAAa;AA4SpE,SAAgB,6BAA6B,QAA2C;CACtF,QAAQ,QAAR;EACE,KAAK,gBACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,UACH,OAAO;CACX;AACF"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/rules/types.ts"],"sourcesContent":["import type { ExternalWorkItemSource } from '../storage/domains/work-items/base.js';\n\nexport type WorkItemSource = 'github-issue' | 'github-pr' | 'linear-issue' | 'manual';\n\nexport function workItemSource(source: ExternalWorkItemSource | null): WorkItemSource {\n if (!source) return 'manual';\n if (source.integrationId === 'linear') return 'linear-issue';\n // Only GitHub and Linear have provider-specific rules; anything else (a Slack\n // thread, say) is a plain work item, not a mislabeled GitHub issue.\n if (source.integrationId !== 'github') return 'manual';\n return source.type === 'pull-request' ? 'github-pr' : 'github-issue';\n}\n\n// Authored outside the write-access circle: a missing trust stamp fails closed until\n// the reconcile sweep backfills it, and Factory's own PRs pass through `factoryAuthored`.\nexport function externallyAuthored(item: { source: string; metadata: Record<string, unknown> | null }): boolean {\n if (item.source !== 'github-pr' && item.source !== 'github-issue') return false;\n if (item.metadata?.factoryAuthored === true) return false;\n return item.metadata?.authorTrusted !== true;\n}\n\n// The board mark claims only what GitHub answered: a missing stamp is silence, not an outside contribution.\nexport function knownExternalAuthor(item: { source: string; metadata: Record<string, unknown> | null }): boolean {\n return externallyAuthored(item) && item.metadata?.authorTrusted === false;\n}\n\nexport function externallyAuthoredWorkItem(item: {\n externalSource: ExternalWorkItemSource | null;\n metadata: Record<string, unknown> | null;\n}): boolean {\n return externallyAuthored({ source: workItemSource(item.externalSource), metadata: item.metadata });\n}\n\nexport const FACTORY_RULE_STAGES = ['intake', 'triage', 'planning', 'execute', 'review', 'done', 'canceled'] as const;\nexport type FactoryRuleStage = (typeof FACTORY_RULE_STAGES)[number];\n\n// Each role and the working stage its run holds the card in. Key order is the\n// seat pipeline order — Resume depth derives from it.\nexport const FACTORY_ROLE_STAGES = {\n triage: 'triage',\n plan: 'planning',\n work: 'execute',\n review: 'review',\n} as const satisfies Record<string, FactoryRuleStage>;\nexport type FactoryRole = keyof typeof FACTORY_ROLE_STAGES;\n\nexport function isFactoryRole(value: string): value is FactoryRole {\n return value in FACTORY_ROLE_STAGES;\n}\n\nexport const FACTORY_TRIAGE_TYPES = [\n 'bug',\n 'feature request',\n 'docs',\n 'question/support',\n 'maintenance',\n 'duplicate',\n 'resolved',\n 'invalid',\n 'spam',\n 'out-of-scope',\n 'other',\n] as const;\nexport type FactoryTriageType = (typeof FACTORY_TRIAGE_TYPES)[number];\n\nexport function isFactoryTriageType(value: unknown): value is FactoryTriageType {\n return typeof value === 'string' && FACTORY_TRIAGE_TYPES.some(type => type === value);\n}\n\nexport function isFactoryRuleStage(value: unknown): value is FactoryRuleStage {\n return typeof value === 'string' && FACTORY_RULE_STAGES.some(stage => stage === value);\n}\n\nexport function factoryRuleStage(stages: readonly string[]): FactoryRuleStage | undefined {\n const stage = stages.length === 1 ? stages[0] : undefined;\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function isTerminalFactoryRuleStage(stages: readonly string[]): boolean {\n const stage = factoryRuleStage(stages);\n return stage === 'done' || stage === 'canceled';\n}\n\n/** Working lanes hold cards with a seat engaged; Intake, Done and Canceled rest them. */\nexport function isWorkingFactoryRuleStage(stage: FactoryRuleStage): boolean {\n return stage !== 'intake' && !isTerminalFactoryRuleStage([stage]);\n}\n\n// Consulted only for the Intake exit: roles don't own lanes, so a card already\n// in a working or terminal lane stays put when a run starts.\nexport function factoryLaneForRole(role: string): FactoryRuleStage | undefined {\n return isFactoryRole(role) ? FACTORY_ROLE_STAGES[role] : undefined;\n}\n\nexport const FACTORY_RULE_BOARDS = ['work', 'review'] as const;\nexport type FactoryRuleBoard = (typeof FACTORY_RULE_BOARDS)[number];\n\nexport const FACTORY_RULE_SOURCES = ['issue', 'pullRequest', 'linearIssue', 'manual'] as const;\nexport type FactoryRuleSource = (typeof FACTORY_RULE_SOURCES)[number];\n\nexport const FACTORY_GITHUB_EVENTS = [\n 'issueOpened',\n 'issueEdited',\n 'issueClosed',\n 'issueCommentCreated',\n 'issueCommentEdited',\n 'issueCommentDeleted',\n 'pullRequestOpened',\n 'pullRequestUpdated',\n 'pullRequestCommentCreated',\n 'pullRequestReviewRequested',\n 'pullRequestReviewSubmitted',\n 'pullRequestMerged',\n 'pullRequestClosed',\n] as const;\nexport type FactoryGithubEventName = (typeof FACTORY_GITHUB_EVENTS)[number];\n\nexport const FACTORY_LINEAR_EVENTS = ['issueObserved', 'issueClosed'] as const;\nexport type FactoryLinearEventName = (typeof FACTORY_LINEAR_EVENTS)[number];\n\nexport type FactoryRuleJsonValue =\n | null\n | boolean\n | number\n | string\n | FactoryRuleJsonValue[]\n | { [key: string]: FactoryRuleJsonValue };\n\nexport interface FactoryRuleItemContext {\n id: string;\n source: WorkItemSource;\n sourceKey: string | null;\n parentWorkItemId: string | null;\n title: string;\n url: string | null;\n stages: readonly string[];\n /** Intake-stamped facts about the source — repository id, reporter login, labels. */\n metadata: Record<string, unknown> | null;\n}\n\nexport type FactoryRuleActor =\n | { type: 'human'; id: string }\n | { type: 'agent'; bindingId: string; role: string }\n | { type: 'github'; login: string; trusted: boolean; factoryAuthored: boolean }\n | { type: 'system'; id: string };\n\nexport interface FactoryRuleIngressIdentity {\n type: 'human' | 'agent' | 'toolResult' | 'github' | 'linear' | 'rule';\n id: string;\n}\n\nexport interface FactoryRuleCausalEntry {\n ingressId: string;\n decisionType: FactoryCommitDecision['type'];\n}\n\nexport interface FactoryRuleContextBase {\n tenant: { orgId: string; projectId: string };\n actor: FactoryRuleActor;\n ingress: FactoryRuleIngressIdentity;\n cause: string;\n causalChain: readonly FactoryRuleCausalEntry[];\n ruleSetVersion: string;\n}\n\nexport interface FactoryBoundRuleContext extends FactoryRuleContextBase {\n item: FactoryRuleItemContext;\n board: FactoryRuleBoard;\n itemRevision: number;\n}\n\nexport interface FactoryStageRuleContext extends FactoryBoundRuleContext {\n source: FactoryRuleSource;\n stage: FactoryRuleStage;\n fromStage: FactoryRuleStage;\n toStage: FactoryRuleStage;\n}\n\nexport interface FactoryToolResultRuleContext extends FactoryBoundRuleContext {\n toolName: string;\n threadId: string;\n assistantMessageId: string;\n toolCallId: string;\n result: {\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n };\n}\n\nexport interface FactoryGithubRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryGithubEventName;\n deliveryId: string;\n factory: { createdAt: string };\n repository: { id: number; fullName: string };\n issue?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n updatedAt?: string;\n assignees?: string[];\n labels?: string[];\n state?: 'open' | 'closed';\n /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */\n stateReason?: string;\n };\n issueChange?: { title: boolean; body: boolean };\n issueComment?: {\n id: number;\n body?: string;\n url?: string;\n author?: string;\n authorType?: string;\n createdAt?: string;\n updatedAt?: string;\n };\n pullRequest?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n state: 'open' | 'closed';\n draft: boolean;\n merged: boolean;\n assignees?: string[];\n requestedReviewers?: string[];\n labels?: string[];\n headBranch: string;\n baseBranch: string;\n };\n /** Present on `pullRequestReviewRequested`: who review was (re-)requested from. */\n reviewRequest?: { reviewer: string; factoryReviewer: boolean };\n /** Present on `pullRequestReviewSubmitted`: the review that was just posted. */\n review?: { id: number; state: string; url: string };\n}\n\nexport interface FactoryLinearRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryLinearEventName;\n issue: {\n id: string;\n identifier: string;\n title: string;\n url: string;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n creator: string | null;\n team: string | null;\n labels: readonly string[];\n createdAt: string;\n updatedAt: string;\n };\n}\n\nexport type FactoryRuleHandler<TContext> = (\n context: Readonly<TContext>,\n) => FactoryRuleDecision | void | Promise<FactoryRuleDecision | void>;\n\nexport interface FactoryBoardRuleLeaf {\n onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;\n onExit?: FactoryRuleHandler<FactoryStageRuleContext>;\n}\n\nexport interface FactoryToolRuleLeaf {\n onResult?: FactoryRuleHandler<FactoryToolResultRuleContext>;\n}\n\nexport interface FactoryGithubRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryGithubRuleContext>;\n}\n\nexport interface FactoryLinearRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryLinearRuleContext>;\n}\n\nexport type FactoryBoardRules = Partial<\n Record<FactoryRuleStage, Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>>>\n>;\n\nexport interface FactoryRules {\n version: string;\n work: FactoryBoardRules;\n review: FactoryBoardRules;\n tools: Record<string, FactoryToolRuleLeaf>;\n github: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;\n linear: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport interface FactoryRulesOverrides {\n work?: FactoryBoardRules;\n review?: FactoryBoardRules;\n tools?: Record<string, FactoryToolRuleLeaf>;\n github?: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;\n linear?: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport type FactoryRuleRejectionCode =\n | 'forbidden'\n | 'invalid_transition'\n | 'missing_binding'\n | 'stale'\n | 'timeout'\n | 'rule_error'\n | 'causal_depth_exceeded'\n | 'repeated_transition'\n | 'approval_required';\n\nexport interface FactoryRuleRejectDecision {\n type: 'reject';\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\ninterface FactoryCommitDecisionBase {\n idempotencyKey: string;\n}\n\nexport interface FactoryTransitionDecision extends FactoryCommitDecisionBase {\n type: 'transition';\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n /**\n * Delivered to the item's active session (waking it if idle) after the\n * transition commits. Skipped when the item has no active run binding, so\n * informational messages never fail the transition.\n */\n message?: { text: string; role?: string };\n /**\n * Runs the stage's entry rules even when the item is already in that stage.\n * A transition to the current stage is normally inert, because most callers\n * are correcting a board into a state it already holds. Re-entry is for the\n * opposite case: the stage's work is in flight and has been invalidated, so\n * it has to start over.\n */\n reenter?: boolean;\n}\n\nexport interface FactoryUpsertLinkedWorkItemDecision extends FactoryCommitDecisionBase {\n type: 'upsertLinkedWorkItem';\n board: FactoryRuleBoard;\n source: WorkItemSource;\n sourceKey: string;\n title: string;\n url: string | null;\n stage: FactoryRuleStage;\n metadata?: Record<string, FactoryRuleJsonValue>;\n}\n\ninterface FactoryInvokeSkillDecisionBase extends FactoryCommitDecisionBase {\n type: 'invokeSkill';\n role: string;\n arguments?: string;\n precedingMessage?: string;\n cancelInFlight?: boolean;\n}\n\n/**\n * Starting an agent run. Most runs activate a skill, because the skill carries\n * the handoff contract later rules match on. A run whose completion is already\n * signalled some other way — Building finishes by opening a pull request, which\n * arrives as its own event — needs no contract, so it can carry a plain prompt\n * instead of an otherwise empty skill.\n */\nexport type FactoryInvokeSkillDecision = FactoryInvokeSkillDecisionBase &\n ({ skillName: string; prompt?: never } | { prompt: string; skillName?: never });\n\nexport interface FactorySendMessageDecision extends FactoryCommitDecisionBase {\n type: 'sendMessage';\n /** Omitted: the card's live session, whichever seat holds it. Required with `prepareBinding`. */\n role?: string;\n message: string;\n priority?: 'medium' | 'high' | 'urgent';\n idleBehavior?: 'persist' | 'wake';\n prepareBinding?: boolean;\n}\n\nexport interface FactoryNotifyDecision extends FactoryCommitDecisionBase {\n type: 'notify';\n title: string;\n body?: string;\n level?: 'info' | 'warning' | 'error';\n}\n\nexport type FactoryCommitDecision =\n | FactoryTransitionDecision\n | FactoryUpsertLinkedWorkItemDecision\n | FactoryInvokeSkillDecision\n | FactorySendMessageDecision\n | FactoryNotifyDecision;\n\nexport type FactoryRuleDecision = FactoryRuleRejectDecision | FactoryCommitDecision;\n\nexport interface FactoryTransitionResultAccepted {\n status: 'accepted';\n transitionId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n decisions: FactoryCommitDecision[];\n}\n\nexport interface FactoryTransitionResultRejected {\n status: 'rejected';\n transitionId: string;\n itemId: string;\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\nexport type FactoryTransitionResult = FactoryTransitionResultAccepted | FactoryTransitionResultRejected;\n\nexport function factoryRuleSourceForWorkItem(source: WorkItemSource): FactoryRuleSource {\n switch (source) {\n case 'github-issue':\n return 'issue';\n case 'github-pr':\n return 'pullRequest';\n case 'linear-issue':\n return 'linearIssue';\n case 'manual':\n return 'manual';\n }\n}\n"],"mappings":";AAIA,SAAgB,eAAe,QAAuD;CACpF,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI,OAAO,kBAAkB,UAAU,OAAO;CAG9C,IAAI,OAAO,kBAAkB,UAAU,OAAO;CAC9C,OAAO,OAAO,SAAS,iBAAiB,cAAc;AACxD;AAIA,SAAgB,mBAAmB,MAA6E;CAC9G,IAAI,KAAK,WAAW,eAAe,KAAK,WAAW,gBAAgB,OAAO;CAC1E,IAAI,KAAK,UAAU,oBAAoB,MAAM,OAAO;CACpD,OAAO,KAAK,UAAU,kBAAkB;AAC1C;AAGA,SAAgB,oBAAoB,MAA6E;CAC/G,OAAO,mBAAmB,IAAI,KAAK,KAAK,UAAU,kBAAkB;AACtE;AAEA,SAAgB,2BAA2B,MAG/B;CACV,OAAO,mBAAmB;EAAE,QAAQ,eAAe,KAAK,cAAc;EAAG,UAAU,KAAK;CAAS,CAAC;AACpG;AAEA,MAAa,sBAAsB;CAAC;CAAU;CAAU;CAAY;CAAW;CAAU;CAAQ;AAAU;AAK3G,MAAa,sBAAsB;CACjC,QAAQ;CACR,MAAM;CACN,MAAM;CACN,QAAQ;AACV;AAGA,SAAgB,cAAc,OAAqC;CACjE,OAAO,SAAS;AAClB;AAEA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,SAAgB,oBAAoB,OAA4C;CAC9E,OAAO,OAAO,UAAU,YAAY,qBAAqB,MAAK,SAAQ,SAAS,KAAK;AACtF;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,OAAO,UAAU,YAAY,oBAAoB,MAAK,UAAS,UAAU,KAAK;AACvF;AAEA,SAAgB,iBAAiB,QAAyD;CACxF,MAAM,QAAQ,OAAO,WAAW,IAAI,OAAO,KAAK,KAAA;CAChD,OAAO,mBAAmB,KAAK,IAAI,QAAQ,KAAA;AAC7C;AAEA,SAAgB,2BAA2B,QAAoC;CAC7E,MAAM,QAAQ,iBAAiB,MAAM;CACrC,OAAO,UAAU,UAAU,UAAU;AACvC;;AAGA,SAAgB,0BAA0B,OAAkC;CAC1E,OAAO,UAAU,YAAY,CAAC,2BAA2B,CAAC,KAAK,CAAC;AAClE;AAIA,SAAgB,mBAAmB,MAA4C;CAC7E,OAAO,cAAc,IAAI,IAAI,oBAAoB,QAAQ,KAAA;AAC3D;AAEA,MAAa,sBAAsB,CAAC,QAAQ,QAAQ;AAGpD,MAAa,uBAAuB;CAAC;CAAS;CAAe;CAAe;AAAQ;AAGpF,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,wBAAwB,CAAC,iBAAiB,aAAa;AA6SpE,SAAgB,6BAA6B,QAA2C;CACtF,QAAQ,QAAR;EACE,KAAK,gBACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,UACH,OAAO;CACX;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EAGb,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,6BAA6B,IAAI,CAAC;AA+B/C,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,0BAA0B;gBAE3B,OAAO,EAAE,MAAM;CAI5B;AAoCD,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,EACd,KAAK,SAAI,EACT,IAAI,cAAoB,GACvB,oBAAoB,CAkCtB;AA8BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAqChF;AAQD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,SAAI,GAAG,mBAAmB,CAuJhG;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE,WAAW,SAAI,GAAG,qBAAqB,EAAE,CAiBjH"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EAGb,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,6BAA6B,IAAI,CAAC;AA+B/C,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,0BAA0B;gBAE3B,OAAO,EAAE,MAAM;CAI5B;AAoCD,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,EACd,KAAK,SAAI,EACT,IAAI,cAAoB,GACvB,oBAAoB,CAkCtB;AA8BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAqChF;AAQD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,SAAI,GAAG,mBAAmB,CA8JhG;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE,WAAW,SAAI,GAAG,qBAAqB,EAAE,CAiBjH"}
@@ -263,10 +263,12 @@ function validateFactoryRuleDecision(value, causalDepth = 0) {
263
263
  ], "Factory message priority");
264
264
  const idleBehavior = value.idleBehavior === void 0 ? void 0 : enumValue(value.idleBehavior, ["persist", "wake"], "Factory message idle behavior");
265
265
  if (value.prepareBinding !== void 0 && typeof value.prepareBinding !== "boolean") throw new FactoryRuleValidationError("Factory message prepareBinding must be a boolean.");
266
+ if (value.prepareBinding === true && value.role === void 0) throw new FactoryRuleValidationError("Factory message prepareBinding requires a role.");
267
+ const role = value.role === void 0 ? void 0 : boundedString(value.role, "Factory message role", MAX_ROLE_LENGTH, IDENTIFIER_RE);
266
268
  return {
267
269
  type,
268
270
  ...commonCommitFields(value),
269
- role: boundedString(value.role, "Factory message role", MAX_ROLE_LENGTH, IDENTIFIER_RE),
271
+ ...role ? { role } : {},
270
272
  message: boundedString(value.message, "Factory message", MAX_MESSAGE_LENGTH),
271
273
  ...priority ? { priority } : {},
272
274
  ...idleBehavior ? { idleBehavior } : {},
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","names":[],"sources":["../../src/rules/validation.ts"],"sourcesContent":["import {\n FACTORY_GITHUB_EVENTS,\n FACTORY_LINEAR_EVENTS,\n FACTORY_RULE_BOARDS,\n FACTORY_RULE_SOURCES,\n FACTORY_RULE_STAGES,\n} from './types.js';\nimport type {\n FactoryBoardRules,\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRules,\n FactoryRuleRejectionCode,\n WorkItemSource,\n} from './types.js';\n\nexport const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;\n\nconst MAX_VERSION_LENGTH = 128;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REASON_LENGTH = 512;\nconst MAX_TITLE_LENGTH = 512;\nconst MAX_MESSAGE_LENGTH = 8_192;\nconst MAX_ARGUMENTS_LENGTH = 4_096;\nconst MAX_ROLE_LENGTH = 32;\nconst MAX_SKILL_NAME_LENGTH = 128;\nconst MAX_SOURCE_KEY_LENGTH = 256;\nconst MAX_URL_LENGTH = 2_048;\nconst MAX_METADATA_JSON_LENGTH = 16_384;\nconst MAX_JSON_DEPTH = 8;\nconst MAX_JSON_COLLECTION_SIZE = 100;\n\nconst IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;\nconst WORK_ITEM_SOURCES: readonly WorkItemSource[] = ['github-issue', 'github-pr', 'linear-issue', 'manual'];\nconst REJECTION_CODES: readonly FactoryRuleRejectionCode[] = [\n 'forbidden',\n 'invalid_transition',\n 'missing_binding',\n 'stale',\n 'timeout',\n 'rule_error',\n 'causal_depth_exceeded',\n 'repeated_transition',\n];\n\nexport class FactoryRuleValidationError extends Error {\n readonly code = 'invalid_factory_rule';\n\n constructor(message: string) {\n super(message);\n this.name = 'FactoryRuleValidationError';\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n const allowed = new Set(keys);\n if (Object.keys(value).some(key => !allowed.has(key))) {\n throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n const normalized = value.trim();\n if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return normalized;\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n if (value === undefined) return undefined;\n return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n if (typeof value !== 'string' || !allowed.includes(value as T)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n value: unknown,\n depth = 0,\n seen = new Set<object>(),\n): FactoryRuleJsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n return value;\n }\n if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n }\n if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n seen.add(value as object);\n try {\n if (Array.isArray(value)) {\n if (value.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n }\n return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n const entries = Object.entries(value);\n if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n }\n const sanitized: Record<string, FactoryRuleJsonValue> = {};\n for (const [key, entry] of entries) {\n const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);\n sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)\n ? '[REDACTED]'\n : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);\n }\n return sanitized;\n } finally {\n seen.delete(value as object);\n }\n}\n\nfunction sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {\n if (value === undefined) return undefined;\n const sanitized = normalizeFactoryRuleJsonValue(value);\n if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');\n if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {\n throw new FactoryRuleValidationError('Rule metadata is too large.');\n }\n return sanitized;\n}\n\nfunction validateBoardRules(rules: unknown, label: string): asserts rules is FactoryBoardRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);\n for (const [stage, sources] of Object.entries(rules)) {\n enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);\n if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);\n for (const [source, leaf] of Object.entries(sources)) {\n enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);\n assertExactKeys(leaf, ['onEnter', 'onExit'], `${label}.${stage}.${source}`);\n for (const handler of Object.values(leaf)) {\n if (handler !== undefined && typeof handler !== 'function') {\n throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);\n }\n }\n }\n }\n}\n\nexport function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');\n assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'github', 'linear'], 'Factory rules');\n boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);\n validateBoardRules(rules.work, 'Factory rules.work');\n validateBoardRules(rules.review, 'Factory rules.review');\n\n if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');\n for (const [toolName, leaf] of Object.entries(rules.tools)) {\n boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);\n if (!isPlainObject(leaf))\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);\n assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);\n if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError('Factory rules.github must be an object.');\n for (const [event, leaf] of Object.entries(rules.github)) {\n enumValue(event, FACTORY_GITHUB_EVENTS, 'Factory GitHub event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.github.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');\n for (const [event, leaf] of Object.entries(rules.linear)) {\n enumValue(event, FACTORY_LINEAR_EVENTS, 'Factory Linear event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.linear.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.linear.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.linear.${event}.onEvent must be a function.`);\n }\n }\n}\n\nfunction commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {\n return {\n idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),\n };\n}\n\nexport function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {\n if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');\n const type = value.type;\n if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');\n\n switch (type) {\n case 'reject': {\n assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');\n return {\n type,\n code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),\n reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),\n };\n }\n case 'transition': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],\n 'Factory transition decision',\n );\n if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {\n throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');\n }\n let message: { text: string; role?: string } | undefined;\n if (value.message !== undefined) {\n if (!isPlainObject(value.message)) {\n throw new FactoryRuleValidationError('Factory transition message must be an object.');\n }\n assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');\n const role =\n value.message.role === undefined\n ? undefined\n : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n message = {\n text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),\n ...(role ? { role } : {}),\n };\n }\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory transition board'),\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory transition stage'),\n ...(message ? { message } : {}),\n ...(value.reenter === true ? { reenter: true } : {}),\n };\n }\n case 'upsertLinkedWorkItem': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'source', 'sourceKey', 'title', 'url', 'stage', 'metadata'],\n 'Factory linked work item decision',\n );\n const url = value.url;\n if (url !== null && (typeof url !== 'string' || url.length > MAX_URL_LENGTH || !/^https?:\\/\\//.test(url))) {\n throw new FactoryRuleValidationError('Factory linked work item URL is invalid.');\n }\n const metadata = sanitizeMetadata(value.metadata);\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory linked work item board'),\n source: enumValue(value.source, WORK_ITEM_SOURCES, 'Factory linked work item source'),\n sourceKey: boundedString(value.sourceKey, 'Factory linked work item sourceKey', MAX_SOURCE_KEY_LENGTH),\n title: boundedString(value.title, 'Factory linked work item title', MAX_TITLE_LENGTH),\n url,\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory linked work item stage'),\n ...(metadata ? { metadata } : {}),\n };\n }\n case 'invokeSkill': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'skillName', 'prompt', 'arguments', 'precedingMessage', 'cancelInFlight'],\n 'Factory invoke skill decision',\n );\n // A run activates a skill or carries a prompt, never both: they are two\n // ways to author the same kickoff message, so accepting both would leave\n // the dispatcher picking a winner.\n if ((value.skillName === undefined) === (value.prompt === undefined)) {\n throw new FactoryRuleValidationError('Factory skill invocation needs exactly one of skillName or prompt.');\n }\n const args = optionalBoundedString(value.arguments, 'Factory skill arguments', MAX_ARGUMENTS_LENGTH);\n const precedingMessage = optionalBoundedString(\n value.precedingMessage,\n 'Factory skill preceding message',\n MAX_MESSAGE_LENGTH,\n );\n if (value.cancelInFlight !== undefined && typeof value.cancelInFlight !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill cancelInFlight must be a boolean.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory skill role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n ...(value.skillName === undefined\n ? { prompt: boundedString(value.prompt, 'Factory skill prompt', MAX_MESSAGE_LENGTH) }\n : { skillName: boundedString(value.skillName, 'Factory skill name', MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE) }),\n ...(args ? { arguments: args } : {}),\n ...(precedingMessage ? { precedingMessage } : {}),\n ...(value.cancelInFlight === true ? { cancelInFlight: true } : {}),\n };\n }\n case 'sendMessage': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'message', 'priority', 'idleBehavior', 'prepareBinding'],\n 'Factory send message decision',\n );\n const priority =\n value.priority === undefined\n ? undefined\n : enumValue(value.priority, ['medium', 'high', 'urgent'] as const, 'Factory message priority');\n const idleBehavior =\n value.idleBehavior === undefined\n ? undefined\n : enumValue(value.idleBehavior, ['persist', 'wake'] as const, 'Factory message idle behavior');\n if (value.prepareBinding !== undefined && typeof value.prepareBinding !== 'boolean') {\n throw new FactoryRuleValidationError('Factory message prepareBinding must be a boolean.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory message role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n message: boundedString(value.message, 'Factory message', MAX_MESSAGE_LENGTH),\n ...(priority ? { priority } : {}),\n ...(idleBehavior ? { idleBehavior } : {}),\n ...(value.prepareBinding === true ? { prepareBinding: true } : {}),\n };\n }\n case 'notify': {\n assertExactKeys(value, ['type', 'idempotencyKey', 'title', 'body', 'level'], 'Factory notify decision');\n const body = optionalBoundedString(value.body, 'Factory notification body', MAX_MESSAGE_LENGTH);\n const level =\n value.level === undefined\n ? undefined\n : enumValue(value.level, ['info', 'warning', 'error'] as const, 'Factory notification level');\n return {\n type,\n ...commonCommitFields(value),\n title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),\n ...(body ? { body } : {}),\n ...(level ? { level } : {}),\n };\n }\n default:\n throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');\n }\n}\n\nexport function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {\n if (values.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Factory rule produced too many decisions.');\n }\n const decisions: FactoryCommitDecision[] = [];\n for (const value of values) {\n const decision = validateFactoryRuleDecision(value, causalDepth);\n if (decision.type === 'reject') {\n throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');\n }\n decisions.push(decision);\n }\n const keys = decisions.map(decision => decision.idempotencyKey);\n if (new Set(keys).size !== keys.length) {\n throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');\n }\n return decisions;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","names":[],"sources":["../../src/rules/validation.ts"],"sourcesContent":["import {\n FACTORY_GITHUB_EVENTS,\n FACTORY_LINEAR_EVENTS,\n FACTORY_RULE_BOARDS,\n FACTORY_RULE_SOURCES,\n FACTORY_RULE_STAGES,\n} from './types.js';\nimport type {\n FactoryBoardRules,\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRules,\n FactoryRuleRejectionCode,\n WorkItemSource,\n} from './types.js';\n\nexport const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;\n\nconst MAX_VERSION_LENGTH = 128;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REASON_LENGTH = 512;\nconst MAX_TITLE_LENGTH = 512;\nconst MAX_MESSAGE_LENGTH = 8_192;\nconst MAX_ARGUMENTS_LENGTH = 4_096;\nconst MAX_ROLE_LENGTH = 32;\nconst MAX_SKILL_NAME_LENGTH = 128;\nconst MAX_SOURCE_KEY_LENGTH = 256;\nconst MAX_URL_LENGTH = 2_048;\nconst MAX_METADATA_JSON_LENGTH = 16_384;\nconst MAX_JSON_DEPTH = 8;\nconst MAX_JSON_COLLECTION_SIZE = 100;\n\nconst IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;\nconst WORK_ITEM_SOURCES: readonly WorkItemSource[] = ['github-issue', 'github-pr', 'linear-issue', 'manual'];\nconst REJECTION_CODES: readonly FactoryRuleRejectionCode[] = [\n 'forbidden',\n 'invalid_transition',\n 'missing_binding',\n 'stale',\n 'timeout',\n 'rule_error',\n 'causal_depth_exceeded',\n 'repeated_transition',\n];\n\nexport class FactoryRuleValidationError extends Error {\n readonly code = 'invalid_factory_rule';\n\n constructor(message: string) {\n super(message);\n this.name = 'FactoryRuleValidationError';\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n const allowed = new Set(keys);\n if (Object.keys(value).some(key => !allowed.has(key))) {\n throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n const normalized = value.trim();\n if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return normalized;\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n if (value === undefined) return undefined;\n return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n if (typeof value !== 'string' || !allowed.includes(value as T)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n value: unknown,\n depth = 0,\n seen = new Set<object>(),\n): FactoryRuleJsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n return value;\n }\n if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n }\n if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n seen.add(value as object);\n try {\n if (Array.isArray(value)) {\n if (value.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n }\n return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n const entries = Object.entries(value);\n if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n }\n const sanitized: Record<string, FactoryRuleJsonValue> = {};\n for (const [key, entry] of entries) {\n const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);\n sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)\n ? '[REDACTED]'\n : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);\n }\n return sanitized;\n } finally {\n seen.delete(value as object);\n }\n}\n\nfunction sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {\n if (value === undefined) return undefined;\n const sanitized = normalizeFactoryRuleJsonValue(value);\n if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');\n if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {\n throw new FactoryRuleValidationError('Rule metadata is too large.');\n }\n return sanitized;\n}\n\nfunction validateBoardRules(rules: unknown, label: string): asserts rules is FactoryBoardRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);\n for (const [stage, sources] of Object.entries(rules)) {\n enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);\n if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);\n for (const [source, leaf] of Object.entries(sources)) {\n enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);\n assertExactKeys(leaf, ['onEnter', 'onExit'], `${label}.${stage}.${source}`);\n for (const handler of Object.values(leaf)) {\n if (handler !== undefined && typeof handler !== 'function') {\n throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);\n }\n }\n }\n }\n}\n\nexport function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');\n assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'github', 'linear'], 'Factory rules');\n boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);\n validateBoardRules(rules.work, 'Factory rules.work');\n validateBoardRules(rules.review, 'Factory rules.review');\n\n if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');\n for (const [toolName, leaf] of Object.entries(rules.tools)) {\n boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);\n if (!isPlainObject(leaf))\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);\n assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);\n if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError('Factory rules.github must be an object.');\n for (const [event, leaf] of Object.entries(rules.github)) {\n enumValue(event, FACTORY_GITHUB_EVENTS, 'Factory GitHub event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.github.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');\n for (const [event, leaf] of Object.entries(rules.linear)) {\n enumValue(event, FACTORY_LINEAR_EVENTS, 'Factory Linear event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.linear.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.linear.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.linear.${event}.onEvent must be a function.`);\n }\n }\n}\n\nfunction commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {\n return {\n idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),\n };\n}\n\nexport function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {\n if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');\n const type = value.type;\n if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');\n\n switch (type) {\n case 'reject': {\n assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');\n return {\n type,\n code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),\n reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),\n };\n }\n case 'transition': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],\n 'Factory transition decision',\n );\n if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {\n throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');\n }\n let message: { text: string; role?: string } | undefined;\n if (value.message !== undefined) {\n if (!isPlainObject(value.message)) {\n throw new FactoryRuleValidationError('Factory transition message must be an object.');\n }\n assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');\n const role =\n value.message.role === undefined\n ? undefined\n : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n message = {\n text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),\n ...(role ? { role } : {}),\n };\n }\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory transition board'),\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory transition stage'),\n ...(message ? { message } : {}),\n ...(value.reenter === true ? { reenter: true } : {}),\n };\n }\n case 'upsertLinkedWorkItem': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'source', 'sourceKey', 'title', 'url', 'stage', 'metadata'],\n 'Factory linked work item decision',\n );\n const url = value.url;\n if (url !== null && (typeof url !== 'string' || url.length > MAX_URL_LENGTH || !/^https?:\\/\\//.test(url))) {\n throw new FactoryRuleValidationError('Factory linked work item URL is invalid.');\n }\n const metadata = sanitizeMetadata(value.metadata);\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory linked work item board'),\n source: enumValue(value.source, WORK_ITEM_SOURCES, 'Factory linked work item source'),\n sourceKey: boundedString(value.sourceKey, 'Factory linked work item sourceKey', MAX_SOURCE_KEY_LENGTH),\n title: boundedString(value.title, 'Factory linked work item title', MAX_TITLE_LENGTH),\n url,\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory linked work item stage'),\n ...(metadata ? { metadata } : {}),\n };\n }\n case 'invokeSkill': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'skillName', 'prompt', 'arguments', 'precedingMessage', 'cancelInFlight'],\n 'Factory invoke skill decision',\n );\n // A run activates a skill or carries a prompt, never both: they are two\n // ways to author the same kickoff message, so accepting both would leave\n // the dispatcher picking a winner.\n if ((value.skillName === undefined) === (value.prompt === undefined)) {\n throw new FactoryRuleValidationError('Factory skill invocation needs exactly one of skillName or prompt.');\n }\n const args = optionalBoundedString(value.arguments, 'Factory skill arguments', MAX_ARGUMENTS_LENGTH);\n const precedingMessage = optionalBoundedString(\n value.precedingMessage,\n 'Factory skill preceding message',\n MAX_MESSAGE_LENGTH,\n );\n if (value.cancelInFlight !== undefined && typeof value.cancelInFlight !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill cancelInFlight must be a boolean.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory skill role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n ...(value.skillName === undefined\n ? { prompt: boundedString(value.prompt, 'Factory skill prompt', MAX_MESSAGE_LENGTH) }\n : { skillName: boundedString(value.skillName, 'Factory skill name', MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE) }),\n ...(args ? { arguments: args } : {}),\n ...(precedingMessage ? { precedingMessage } : {}),\n ...(value.cancelInFlight === true ? { cancelInFlight: true } : {}),\n };\n }\n case 'sendMessage': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'message', 'priority', 'idleBehavior', 'prepareBinding'],\n 'Factory send message decision',\n );\n const priority =\n value.priority === undefined\n ? undefined\n : enumValue(value.priority, ['medium', 'high', 'urgent'] as const, 'Factory message priority');\n const idleBehavior =\n value.idleBehavior === undefined\n ? undefined\n : enumValue(value.idleBehavior, ['persist', 'wake'] as const, 'Factory message idle behavior');\n if (value.prepareBinding !== undefined && typeof value.prepareBinding !== 'boolean') {\n throw new FactoryRuleValidationError('Factory message prepareBinding must be a boolean.');\n }\n if (value.prepareBinding === true && value.role === undefined) {\n throw new FactoryRuleValidationError('Factory message prepareBinding requires a role.');\n }\n const role =\n value.role === undefined\n ? undefined\n : boundedString(value.role, 'Factory message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n return {\n type,\n ...commonCommitFields(value),\n ...(role ? { role } : {}),\n message: boundedString(value.message, 'Factory message', MAX_MESSAGE_LENGTH),\n ...(priority ? { priority } : {}),\n ...(idleBehavior ? { idleBehavior } : {}),\n ...(value.prepareBinding === true ? { prepareBinding: true } : {}),\n };\n }\n case 'notify': {\n assertExactKeys(value, ['type', 'idempotencyKey', 'title', 'body', 'level'], 'Factory notify decision');\n const body = optionalBoundedString(value.body, 'Factory notification body', MAX_MESSAGE_LENGTH);\n const level =\n value.level === undefined\n ? undefined\n : enumValue(value.level, ['info', 'warning', 'error'] as const, 'Factory notification level');\n return {\n type,\n ...commonCommitFields(value),\n title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),\n ...(body ? { body } : {}),\n ...(level ? { level } : {}),\n };\n }\n default:\n throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');\n }\n}\n\nexport function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {\n if (values.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Factory rule produced too many decisions.');\n }\n const decisions: FactoryCommitDecision[] = [];\n for (const value of values) {\n const decision = validateFactoryRuleDecision(value, causalDepth);\n if (decision.type === 'reject') {\n throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');\n }\n decisions.push(decision);\n }\n const keys = decisions.map(decision => decision.idempotencyKey);\n if (new Set(keys).size !== keys.length) {\n throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');\n }\n return decisions;\n}\n"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Lower attention tier: one collapsed row per (work item, participant), bumped
3
+ * per comment to stale-date read receipts. Participants derive per write —
4
+ * recent authors + creator, minus the comment's mentions; nothing subscribes.
5
+ */
6
+ import type { FactoryStorageOps } from '@mastra/core/storage';
7
+ import type { FactoryMentionRef, WorkItemCommentRow } from './base.js';
8
+ /** Ceiling: an author silent for this many comments stops receiving; a real participants table is the upgrade. */
9
+ export declare const PARTICIPANT_SCAN_LIMIT = 200;
10
+ export interface WorkItemActivityRow {
11
+ id: string;
12
+ orgId: string;
13
+ factoryProjectId: string;
14
+ workItemId: string;
15
+ participantId: string;
16
+ occurrence: number;
17
+ latestCommentId: string;
18
+ latestAuthorId: string;
19
+ latestAuthorName: string | null;
20
+ occurredAt: Date;
21
+ }
22
+ export declare function fanOutWorkItemActivity(ops: FactoryStorageOps, { comment, recentAuthors, createdBy, mentions, now, }: {
23
+ comment: WorkItemCommentRow;
24
+ recentAuthors: string[];
25
+ createdBy: string;
26
+ mentions: FactoryMentionRef[];
27
+ now?: Date;
28
+ }): Promise<void>;
29
+ /** One bounded keyset page of a participant's activity rows, newest-first. */
30
+ export declare function listActivityForUser(ops: FactoryStorageOps, { orgId, factoryProjectId, userId, before, limit, }: {
31
+ orgId: string;
32
+ factoryProjectId: string;
33
+ userId: string;
34
+ before?: {
35
+ occurredAt: Date;
36
+ id: string;
37
+ };
38
+ limit: number;
39
+ }): Promise<WorkItemActivityRow[]>;
40
+ //# sourceMappingURL=activity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/comments/activity.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAKvE,kHAAkH;AAClH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,IAAI,CAAC;CAClB;AA0GD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,iBAAiB,EACtB,EACE,OAAO,EACP,aAAa,EACb,SAAS,EACT,QAAQ,EACR,GAAgB,GACjB,EAAE;IACD,OAAO,EAAE,kBAAkB,CAAC;IAC5B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,8EAA8E;AAC9E,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,iBAAiB,EACtB,EACE,KAAK,EACL,gBAAgB,EAChB,MAAM,EACN,MAAM,EACN,KAAK,GACN,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QAAE,UAAU,EAAE,IAAI,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;CACf,GACA,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAchC"}
@@ -0,0 +1,94 @@
1
+ import { isMentionableActorId } from "./actor.js";
2
+ import { UniqueViolationError } from "@mastra/core/storage";
3
+ //#region src/storage/domains/comments/activity.ts
4
+ /** The actor rule-materialized work items are created by; a phantom, never a participant. */
5
+ const RULE_DISPATCHER_ACTOR_ID = "factory-rule-dispatcher";
6
+ /** Ceiling: an author silent for this many comments stops receiving; a real participants table is the upgrade. */
7
+ const PARTICIPANT_SCAN_LIMIT = 200;
8
+ function toWorkItemActivity(row) {
9
+ return {
10
+ id: row.id,
11
+ orgId: row.org_id,
12
+ factoryProjectId: row.factory_project_id,
13
+ workItemId: row.work_item_id,
14
+ participantId: row.participant_id,
15
+ occurrence: Number(row.occurrence),
16
+ latestCommentId: row.latest_comment_id,
17
+ latestAuthorId: row.latest_author_id,
18
+ latestAuthorName: row.latest_author_name,
19
+ occurredAt: row.occurred_at
20
+ };
21
+ }
22
+ /**
23
+ * Everyone who should hear about `comment`: the item's recent human comment
24
+ * authors plus its creator, minus the author themselves and anyone the comment
25
+ * already mentions.
26
+ */
27
+ function activityParticipants({ recentAuthors, createdBy, comment, mentions }) {
28
+ const excluded = /* @__PURE__ */ new Set([comment.author.id, ...mentions.map((mention) => mention.id)]);
29
+ const candidates = [...recentAuthors, createdBy].filter((id) => id !== RULE_DISPATCHER_ACTOR_ID && isMentionableActorId(id) && !excluded.has(id));
30
+ return [...new Set(candidates)];
31
+ }
32
+ /**
33
+ * Bumps one participant's row, or opens it at occurrence 1. The pointer only
34
+ * ever moves forward, so a backdated ingest can't bury a row someone read.
35
+ *
36
+ * `UniqueViolationError` stays contained here: `create()`'s recover path tests
37
+ * only `instanceof` and would misread an escaped one as a source-key replay.
38
+ */
39
+ async function bumpParticipant(ops, comment, participantId, now) {
40
+ const where = {
41
+ work_item_id: comment.workItemId,
42
+ participant_id: participantId,
43
+ org_id: comment.orgId,
44
+ factory_project_id: comment.factoryProjectId
45
+ };
46
+ const pointer = {
47
+ latest_comment_id: comment.id,
48
+ latest_author_id: comment.author.id,
49
+ latest_author_name: comment.author.displayName ?? null,
50
+ occurred_at: comment.occurredAt
51
+ };
52
+ const bump = () => ops.updateAtomic("work_item_activity", where, (current) => ({
53
+ occurrence: Number(current.occurrence) + 1,
54
+ ...current.occurred_at.getTime() <= comment.occurredAt.getTime() ? pointer : {},
55
+ updated_at: now
56
+ }));
57
+ if (await bump()) return;
58
+ try {
59
+ await ops.insertOne("work_item_activity", {
60
+ ...where,
61
+ ...pointer,
62
+ occurrence: 1,
63
+ created_at: now,
64
+ updated_at: now
65
+ });
66
+ } catch (error) {
67
+ if (!(error instanceof UniqueViolationError)) throw error;
68
+ await bump();
69
+ }
70
+ }
71
+ async function fanOutWorkItemActivity(ops, { comment, recentAuthors, createdBy, mentions, now = /* @__PURE__ */ new Date() }) {
72
+ for (const participantId of activityParticipants({
73
+ recentAuthors,
74
+ createdBy,
75
+ comment,
76
+ mentions
77
+ })) await bumpParticipant(ops, comment, participantId, now);
78
+ }
79
+ /** One bounded keyset page of a participant's activity rows, newest-first. */
80
+ async function listActivityForUser(ops, { orgId, factoryProjectId, userId, before, limit }) {
81
+ return (await ops.findMany("work_item_activity", {
82
+ org_id: orgId,
83
+ factory_project_id: factoryProjectId,
84
+ participant_id: userId
85
+ }, {
86
+ orderBy: [["occurred_at", "desc"], ["id", "desc"]],
87
+ limit,
88
+ ...before ? { cursor: { values: [before.occurredAt, before.id] } } : {}
89
+ })).map(toWorkItemActivity);
90
+ }
91
+ //#endregion
92
+ export { PARTICIPANT_SCAN_LIMIT, fanOutWorkItemActivity, listActivityForUser };
93
+
94
+ //# sourceMappingURL=activity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity.js","names":[],"sources":["../../../../src/storage/domains/comments/activity.ts"],"sourcesContent":["/**\n * Lower attention tier: one collapsed row per (work item, participant), bumped\n * per comment to stale-date read receipts. Participants derive per write —\n * recent authors + creator, minus the comment's mentions; nothing subscribes.\n */\n\nimport type { FactoryStorageOps } from '@mastra/core/storage';\nimport { UniqueViolationError } from '@mastra/core/storage';\n\nimport { isMentionableActorId } from './actor.js';\nimport type { FactoryMentionRef, WorkItemCommentRow } from './base.js';\n\n/** The actor rule-materialized work items are created by; a phantom, never a participant. */\nconst RULE_DISPATCHER_ACTOR_ID = 'factory-rule-dispatcher';\n\n/** Ceiling: an author silent for this many comments stops receiving; a real participants table is the upgrade. */\nexport const PARTICIPANT_SCAN_LIMIT = 200;\n\nexport interface WorkItemActivityRow {\n id: string;\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n participantId: string;\n occurrence: number;\n latestCommentId: string;\n latestAuthorId: string;\n latestAuthorName: string | null;\n occurredAt: Date;\n}\n\ninterface WorkItemActivityDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n factory_project_id: string;\n work_item_id: string;\n participant_id: string;\n occurrence: number;\n latest_comment_id: string;\n latest_author_id: string;\n latest_author_name: string | null;\n occurred_at: Date;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction toWorkItemActivity(row: WorkItemActivityDbRow): WorkItemActivityRow {\n return {\n id: row.id,\n orgId: row.org_id,\n factoryProjectId: row.factory_project_id,\n workItemId: row.work_item_id,\n participantId: row.participant_id,\n occurrence: Number(row.occurrence),\n latestCommentId: row.latest_comment_id,\n latestAuthorId: row.latest_author_id,\n latestAuthorName: row.latest_author_name,\n occurredAt: row.occurred_at,\n };\n}\n\n/**\n * Everyone who should hear about `comment`: the item's recent human comment\n * authors plus its creator, minus the author themselves and anyone the comment\n * already mentions.\n */\nfunction activityParticipants({\n recentAuthors,\n createdBy,\n comment,\n mentions,\n}: {\n recentAuthors: string[];\n createdBy: string;\n comment: WorkItemCommentRow;\n mentions: FactoryMentionRef[];\n}): string[] {\n const excluded = new Set([comment.author.id, ...mentions.map(mention => mention.id)]);\n const candidates = [...recentAuthors, createdBy].filter(\n id => id !== RULE_DISPATCHER_ACTOR_ID && isMentionableActorId(id) && !excluded.has(id),\n );\n return [...new Set(candidates)];\n}\n\n/**\n * Bumps one participant's row, or opens it at occurrence 1. The pointer only\n * ever moves forward, so a backdated ingest can't bury a row someone read.\n *\n * `UniqueViolationError` stays contained here: `create()`'s recover path tests\n * only `instanceof` and would misread an escaped one as a source-key replay.\n */\nasync function bumpParticipant(\n ops: FactoryStorageOps,\n comment: WorkItemCommentRow,\n participantId: string,\n now: Date,\n): Promise<void> {\n const where = {\n work_item_id: comment.workItemId,\n participant_id: participantId,\n org_id: comment.orgId,\n factory_project_id: comment.factoryProjectId,\n };\n const pointer = {\n latest_comment_id: comment.id,\n latest_author_id: comment.author.id,\n latest_author_name: comment.author.displayName ?? null,\n occurred_at: comment.occurredAt,\n };\n // Mutator reads only `current`: a query inside `updateAtomic` checks out a\n // second pool connection while the first is held open.\n const bump = () =>\n ops.updateAtomic<WorkItemActivityDbRow>('work_item_activity', where, current => ({\n occurrence: Number(current.occurrence) + 1,\n ...(current.occurred_at.getTime() <= comment.occurredAt.getTime() ? pointer : {}),\n updated_at: now,\n }));\n\n if (await bump()) return;\n try {\n await ops.insertOne<WorkItemActivityDbRow>('work_item_activity', {\n ...where,\n ...pointer,\n occurrence: 1,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n // Someone opened the row between the miss and the insert. A second miss\n // means a concurrent item delete purged it — nothing left to bump.\n await bump();\n }\n}\n\nexport async function fanOutWorkItemActivity(\n ops: FactoryStorageOps,\n {\n comment,\n recentAuthors,\n createdBy,\n mentions,\n now = new Date(),\n }: {\n comment: WorkItemCommentRow;\n recentAuthors: string[];\n createdBy: string;\n mentions: FactoryMentionRef[];\n now?: Date;\n },\n): Promise<void> {\n for (const participantId of activityParticipants({ recentAuthors, createdBy, comment, mentions })) {\n await bumpParticipant(ops, comment, participantId, now);\n }\n}\n\n/** One bounded keyset page of a participant's activity rows, newest-first. */\nexport async function listActivityForUser(\n ops: FactoryStorageOps,\n {\n orgId,\n factoryProjectId,\n userId,\n before,\n limit,\n }: {\n orgId: string;\n factoryProjectId: string;\n userId: string;\n before?: { occurredAt: Date; id: string };\n limit: number;\n },\n): Promise<WorkItemActivityRow[]> {\n const rows = await ops.findMany<WorkItemActivityDbRow>(\n 'work_item_activity',\n { org_id: orgId, factory_project_id: factoryProjectId, participant_id: userId },\n {\n orderBy: [\n ['occurred_at', 'desc'],\n ['id', 'desc'],\n ],\n limit,\n ...(before ? { cursor: { values: [before.occurredAt, before.id] } } : {}),\n },\n );\n return rows.map(toWorkItemActivity);\n}\n"],"mappings":";;;;AAaA,MAAM,2BAA2B;;AAGjC,MAAa,yBAAyB;AA8BtC,SAAS,mBAAmB,KAAiD;CAC3E,OAAO;EACL,IAAI,IAAI;EACR,OAAO,IAAI;EACX,kBAAkB,IAAI;EACtB,YAAY,IAAI;EAChB,eAAe,IAAI;EACnB,YAAY,OAAO,IAAI,UAAU;EACjC,iBAAiB,IAAI;EACrB,gBAAgB,IAAI;EACpB,kBAAkB,IAAI;EACtB,YAAY,IAAI;CAClB;AACF;;;;;;AAOA,SAAS,qBAAqB,EAC5B,eACA,WACA,SACA,YAMW;CACX,MAAM,2BAAW,IAAI,IAAI,CAAC,QAAQ,OAAO,IAAI,GAAG,SAAS,KAAI,YAAW,QAAQ,EAAE,CAAC,CAAC;CACpF,MAAM,aAAa,CAAC,GAAG,eAAe,SAAS,CAAC,CAAC,QAC/C,OAAM,OAAO,4BAA4B,qBAAqB,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE,CACvF;CACA,OAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;;;;;;;;AASA,eAAe,gBACb,KACA,SACA,eACA,KACe;CACf,MAAM,QAAQ;EACZ,cAAc,QAAQ;EACtB,gBAAgB;EAChB,QAAQ,QAAQ;EAChB,oBAAoB,QAAQ;CAC9B;CACA,MAAM,UAAU;EACd,mBAAmB,QAAQ;EAC3B,kBAAkB,QAAQ,OAAO;EACjC,oBAAoB,QAAQ,OAAO,eAAe;EAClD,aAAa,QAAQ;CACvB;CAGA,MAAM,aACJ,IAAI,aAAoC,sBAAsB,QAAO,aAAY;EAC/E,YAAY,OAAO,QAAQ,UAAU,IAAI;EACzC,GAAI,QAAQ,YAAY,QAAQ,KAAK,QAAQ,WAAW,QAAQ,IAAI,UAAU,CAAC;EAC/E,YAAY;CACd,EAAE;CAEJ,IAAI,MAAM,KAAK,GAAG;CAClB,IAAI;EACF,MAAM,IAAI,UAAiC,sBAAsB;GAC/D,GAAG;GACH,GAAG;GACH,YAAY;GACZ,YAAY;GACZ,YAAY;EACd,CAAC;CACH,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;EAGpD,MAAM,KAAK;CACb;AACF;AAEA,eAAsB,uBACpB,KACA,EACE,SACA,eACA,WACA,UACA,sBAAM,IAAI,KAAK,KAQF;CACf,KAAK,MAAM,iBAAiB,qBAAqB;EAAE;EAAe;EAAW;EAAS;CAAS,CAAC,GAC9F,MAAM,gBAAgB,KAAK,SAAS,eAAe,GAAG;AAE1D;;AAGA,eAAsB,oBACpB,KACA,EACE,OACA,kBACA,QACA,QACA,SAQ8B;CAahC,QAAO,MAZY,IAAI,SACrB,sBACA;EAAE,QAAQ;EAAO,oBAAoB;EAAkB,gBAAgB;CAAO,GAC9E;EACE,SAAS,CACP,CAAC,eAAe,MAAM,GACtB,CAAC,MAAM,MAAM,CACf;EACA;EACA,GAAI,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,YAAY,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC;CACzE,CACF,EAAA,CACY,IAAI,kBAAkB;AACpC"}
@@ -12,8 +12,10 @@
12
12
  */
13
13
  import { FactoryStorageDomain } from '@mastra/core/storage';
14
14
  import type { ExternalWorkItemSource } from '../work-items/base.js';
15
+ import type { WorkItemActivityRow } from './activity.js';
15
16
  import type { FactoryActorRef } from './actor.js';
16
- export { WORK_ITEM_COMMENT_MENTIONS_SCHEMA, WORK_ITEM_COMMENTS_SCHEMA } from './schema.js';
17
+ export type { WorkItemActivityRow } from './activity.js';
18
+ export { WORK_ITEM_ACTIVITY_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA, WORK_ITEM_COMMENTS_SCHEMA } from './schema.js';
17
19
  export type WorkItemCommentKind = 'comment';
18
20
  export interface FactoryMentionRef {
19
21
  kind: 'user';
@@ -146,6 +148,16 @@ export declare class WorkItemCommentsStorage extends FactoryStorageDomain {
146
148
  init(): Promise<void>;
147
149
  dangerouslyClearAll(): Promise<void>;
148
150
  create(input: CreateWorkItemCommentInput): Promise<WorkItemCommentRow>;
151
+ listActivityForUser(args: {
152
+ orgId: string;
153
+ factoryProjectId: string;
154
+ userId: string;
155
+ before?: {
156
+ occurredAt: Date;
157
+ id: string;
158
+ };
159
+ limit: number;
160
+ }): Promise<WorkItemActivityRow[]>;
149
161
  /**
150
162
  * Idempotent counter refresh on the parent work item: both columns are read
151
163
  * back off the comments (never incremented or stamped with the wall clock —
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/comments/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/comments/base.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { WORK_ITEM_COMMENTS_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA } from "./schema.js";
1
+ import { WORK_ITEM_ACTIVITY_SCHEMA, WORK_ITEM_COMMENTS_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA } from "./schema.js";
2
+ import { fanOutWorkItemActivity, listActivityForUser } from "./activity.js";
2
3
  import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
3
4
  //#region src/storage/domains/comments/base.ts
4
5
  /**
@@ -135,9 +136,14 @@ var WorkItemCommentsStorage = class extends FactoryStorageDomain {
135
136
  super("work-item-comments");
136
137
  }
137
138
  async init() {
138
- await this.ensureCollections([WORK_ITEM_COMMENTS_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA]);
139
+ await this.ensureCollections([
140
+ WORK_ITEM_COMMENTS_SCHEMA,
141
+ WORK_ITEM_COMMENT_MENTIONS_SCHEMA,
142
+ WORK_ITEM_ACTIVITY_SCHEMA
143
+ ]);
139
144
  }
140
145
  async dangerouslyClearAll() {
146
+ await this.ops.deleteMany("work_item_activity", {});
141
147
  await this.ops.deleteMany("work_item_comment_mentions", {});
142
148
  await this.ops.deleteMany("work_item_comments", {});
143
149
  }
@@ -196,12 +202,44 @@ var WorkItemCommentsStorage = class extends FactoryStorageDomain {
196
202
  }
197
203
  return this.#insertWithMentions(row);
198
204
  }
205
+ /**
206
+ * The one insert path a genuinely new comment takes — `create()`'s recover
207
+ * branches never reach it, so a replay writes no mention or activity rows.
208
+ */
199
209
  async #insertWithMentions(row) {
200
210
  const comment = toComment(await this.ops.insertOne("work_item_comments", row));
201
211
  await this.#writeMentionRows(comment, comment.mentions);
212
+ await this.#fanOutActivity(comment);
202
213
  return comment;
203
214
  }
204
215
  /**
216
+ * Participants come off the item's recent authors, so the scan happens here
217
+ * rather than in the fan-out, which must stay free of its own queries.
218
+ */
219
+ async #fanOutActivity(comment) {
220
+ const item = await this.ops.findOne("work_items", {
221
+ id: comment.workItemId,
222
+ org_id: comment.orgId,
223
+ factory_project_id: comment.factoryProjectId
224
+ });
225
+ if (!item) return;
226
+ const recent = await this.listRecent({
227
+ orgId: comment.orgId,
228
+ factoryProjectId: comment.factoryProjectId,
229
+ workItemId: comment.workItemId,
230
+ limit: 200
231
+ });
232
+ await fanOutWorkItemActivity(this.ops, {
233
+ comment,
234
+ recentAuthors: recent.filter((row) => row.author.kind === "user").map((row) => row.author.id),
235
+ createdBy: item.created_by,
236
+ mentions: comment.mentions
237
+ });
238
+ }
239
+ async listActivityForUser(args) {
240
+ return listActivityForUser(this.ops, args);
241
+ }
242
+ /**
205
243
  * Idempotent counter refresh on the parent work item: both columns are read
206
244
  * back off the comments (never incremented or stamped with the wall clock —
207
245
  * replays and races double an increment, and a replayed create would move a
@@ -485,6 +523,6 @@ var WorkItemCommentsStorage = class extends FactoryStorageDomain {
485
523
  }
486
524
  };
487
525
  //#endregion
488
- export { CommentTokenConflictError, MAX_COMMENT_BODY_LENGTH, MAX_COMMENT_MENTIONS, MAX_COMMENT_QUOTE_LENGTH, WORK_ITEM_COMMENTS_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA, WorkItemCommentsStorage, clampCommentLimit, commentBodyError, commentSourceKey, decodeCommentCursor, encodeCommentCursor, supersedesFeedActivity };
526
+ export { CommentTokenConflictError, MAX_COMMENT_BODY_LENGTH, MAX_COMMENT_MENTIONS, MAX_COMMENT_QUOTE_LENGTH, WORK_ITEM_ACTIVITY_SCHEMA, WORK_ITEM_COMMENTS_SCHEMA, WORK_ITEM_COMMENT_MENTIONS_SCHEMA, WorkItemCommentsStorage, clampCommentLimit, commentBodyError, commentSourceKey, decodeCommentCursor, encodeCommentCursor, supersedesFeedActivity };
489
527
 
490
528
  //# sourceMappingURL=base.js.map