@mastra/factory 0.26.0-alpha.1 → 0.26.0-alpha.3

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 (180) hide show
  1. package/README.md +74 -7
  2. package/dist/boards/define-board.d.ts +19 -0
  3. package/dist/boards/define-board.d.ts.map +1 -1
  4. package/dist/boards/define-board.js +41 -9
  5. package/dist/boards/define-board.js.map +1 -1
  6. package/dist/boards/project-boards.d.ts +78 -0
  7. package/dist/boards/project-boards.d.ts.map +1 -0
  8. package/dist/boards/project-boards.js +194 -0
  9. package/dist/boards/project-boards.js.map +1 -0
  10. package/dist/boards/review.js +2 -2
  11. package/dist/boards/review.js.map +1 -1
  12. package/dist/boards/stored-board.d.ts +101 -0
  13. package/dist/boards/stored-board.d.ts.map +1 -0
  14. package/dist/boards/stored-board.js +139 -0
  15. package/dist/boards/stored-board.js.map +1 -0
  16. package/dist/boards/work.d.ts.map +1 -1
  17. package/dist/boards/work.js +6 -3
  18. package/dist/boards/work.js.map +1 -1
  19. package/dist/capabilities/intake.d.ts +6 -0
  20. package/dist/capabilities/intake.d.ts.map +1 -1
  21. package/dist/environment/build.d.ts +1 -1
  22. package/dist/environment/build.d.ts.map +1 -1
  23. package/dist/environment/build.js +2 -0
  24. package/dist/environment/build.js.map +1 -1
  25. package/dist/factory.d.ts +15 -2
  26. package/dist/factory.d.ts.map +1 -1
  27. package/dist/factory.js +51 -12
  28. package/dist/factory.js.map +1 -1
  29. package/dist/integrations/github/integration.d.ts +5 -4
  30. package/dist/integrations/github/integration.d.ts.map +1 -1
  31. package/dist/integrations/github/integration.js +10 -8
  32. package/dist/integrations/github/integration.js.map +1 -1
  33. package/dist/integrations/github/sandbox.d.ts +2 -2
  34. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  35. package/dist/integrations/github/sandbox.js +6 -1
  36. package/dist/integrations/github/sandbox.js.map +1 -1
  37. package/dist/integrations/jira/binding-scope.d.ts +30 -0
  38. package/dist/integrations/jira/binding-scope.d.ts.map +1 -0
  39. package/dist/integrations/jira/binding-scope.js +50 -0
  40. package/dist/integrations/jira/binding-scope.js.map +1 -0
  41. package/dist/integrations/jira/claim.d.ts +12 -0
  42. package/dist/integrations/jira/claim.d.ts.map +1 -1
  43. package/dist/integrations/jira/claim.js +24 -1
  44. package/dist/integrations/jira/claim.js.map +1 -1
  45. package/dist/integrations/jira/intake-jql.d.ts +32 -0
  46. package/dist/integrations/jira/intake-jql.d.ts.map +1 -0
  47. package/dist/integrations/jira/intake-jql.js +57 -0
  48. package/dist/integrations/jira/intake-jql.js.map +1 -0
  49. package/dist/integrations/jira/integration.d.ts +4 -3
  50. package/dist/integrations/jira/integration.d.ts.map +1 -1
  51. package/dist/integrations/jira/integration.js +14 -21
  52. package/dist/integrations/jira/integration.js.map +1 -1
  53. package/dist/integrations/jira/routes.d.ts +3 -0
  54. package/dist/integrations/jira/routes.d.ts.map +1 -1
  55. package/dist/integrations/jira/routes.js +16 -29
  56. package/dist/integrations/jira/routes.js.map +1 -1
  57. package/dist/integrations/jira/rules.d.ts.map +1 -1
  58. package/dist/integrations/jira/rules.js +8 -13
  59. package/dist/integrations/jira/rules.js.map +1 -1
  60. package/dist/integrations/platform/github/integration.d.ts +5 -0
  61. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  62. package/dist/integrations/platform/github/integration.js +7 -0
  63. package/dist/integrations/platform/github/integration.js.map +1 -1
  64. package/dist/integrations/platform/jira/integration.d.ts +7 -1
  65. package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
  66. package/dist/integrations/platform/jira/integration.js +26 -24
  67. package/dist/integrations/platform/jira/integration.js.map +1 -1
  68. package/dist/integrations/platform/jira/routes.d.ts +3 -0
  69. package/dist/integrations/platform/jira/routes.d.ts.map +1 -1
  70. package/dist/integrations/platform/jira/routes.js +17 -28
  71. package/dist/integrations/platform/jira/routes.js.map +1 -1
  72. package/dist/routes/attention-providers.d.ts +2 -1
  73. package/dist/routes/attention-providers.d.ts.map +1 -1
  74. package/dist/routes/attention-providers.js +3 -2
  75. package/dist/routes/attention-providers.js.map +1 -1
  76. package/dist/routes/attention.d.ts +2 -1
  77. package/dist/routes/attention.d.ts.map +1 -1
  78. package/dist/routes/attention.js.map +1 -1
  79. package/dist/routes/boards.d.ts +30 -0
  80. package/dist/routes/boards.d.ts.map +1 -0
  81. package/dist/routes/boards.js +272 -0
  82. package/dist/routes/boards.js.map +1 -0
  83. package/dist/routes/contracts.d.ts +249 -0
  84. package/dist/routes/contracts.d.ts.map +1 -1
  85. package/dist/routes/contracts.js +107 -35
  86. package/dist/routes/contracts.js.map +1 -1
  87. package/dist/routes/intake.d.ts +15 -10
  88. package/dist/routes/intake.d.ts.map +1 -1
  89. package/dist/routes/intake.js +109 -22
  90. package/dist/routes/intake.js.map +1 -1
  91. package/dist/routes/projects.d.ts +3 -0
  92. package/dist/routes/projects.d.ts.map +1 -1
  93. package/dist/routes/projects.js +10 -1
  94. package/dist/routes/projects.js.map +1 -1
  95. package/dist/routes/surface.d.ts +10 -3
  96. package/dist/routes/surface.d.ts.map +1 -1
  97. package/dist/routes/surface.js +13 -1
  98. package/dist/routes/surface.js.map +1 -1
  99. package/dist/routes/tenant-credentials.d.ts +9 -0
  100. package/dist/routes/tenant-credentials.d.ts.map +1 -1
  101. package/dist/routes/tenant-credentials.js +24 -2
  102. package/dist/routes/tenant-credentials.js.map +1 -1
  103. package/dist/routes/work-items.d.ts +3 -1
  104. package/dist/routes/work-items.d.ts.map +1 -1
  105. package/dist/routes/work-items.js +64 -42
  106. package/dist/routes/work-items.js.map +1 -1
  107. package/dist/rules/dispatch-errors.d.ts.map +1 -1
  108. package/dist/rules/dispatch-errors.js +4 -0
  109. package/dist/rules/dispatch-errors.js.map +1 -1
  110. package/dist/rules/dispatcher.d.ts +7 -1
  111. package/dist/rules/dispatcher.d.ts.map +1 -1
  112. package/dist/rules/dispatcher.js +70 -34
  113. package/dist/rules/dispatcher.js.map +1 -1
  114. package/dist/rules/processor.d.ts +3 -1
  115. package/dist/rules/processor.d.ts.map +1 -1
  116. package/dist/rules/processor.js +11 -6
  117. package/dist/rules/processor.js.map +1 -1
  118. package/dist/rules/tools.d.ts +2 -1
  119. package/dist/rules/tools.d.ts.map +1 -1
  120. package/dist/rules/tools.js +2 -1
  121. package/dist/rules/tools.js.map +1 -1
  122. package/dist/rules/transition-service.d.ts +3 -1
  123. package/dist/rules/transition-service.d.ts.map +1 -1
  124. package/dist/rules/transition-service.js +12 -12
  125. package/dist/rules/transition-service.js.map +1 -1
  126. package/dist/sandbox/local-factory-sandbox.d.ts +4 -1
  127. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -1
  128. package/dist/sandbox/local-factory-sandbox.js +1 -1
  129. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  130. package/dist/storage/domains/audit/actions.d.ts +1 -0
  131. package/dist/storage/domains/audit/actions.d.ts.map +1 -1
  132. package/dist/storage/domains/audit/actions.js +6 -1
  133. package/dist/storage/domains/audit/actions.js.map +1 -1
  134. package/dist/storage/domains/boards/base.d.ts +110 -0
  135. package/dist/storage/domains/boards/base.d.ts.map +1 -0
  136. package/dist/storage/domains/boards/base.js +292 -0
  137. package/dist/storage/domains/boards/base.js.map +1 -0
  138. package/dist/storage/domains/boards/schemas.d.ts +11 -0
  139. package/dist/storage/domains/boards/schemas.d.ts.map +1 -0
  140. package/dist/storage/domains/boards/schemas.js +106 -0
  141. package/dist/storage/domains/boards/schemas.js.map +1 -0
  142. package/dist/storage/domains/credentials/base.d.ts +4 -1
  143. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  144. package/dist/storage/domains/credentials/base.js +5 -2
  145. package/dist/storage/domains/credentials/base.js.map +1 -1
  146. package/dist/storage/domains/intake/base.d.ts +86 -5
  147. package/dist/storage/domains/intake/base.d.ts.map +1 -1
  148. package/dist/storage/domains/intake/base.js +298 -59
  149. package/dist/storage/domains/intake/base.js.map +1 -1
  150. package/dist/storage/domains/projects/base.d.ts +2 -1
  151. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  152. package/dist/storage/domains/projects/base.js +15 -10
  153. package/dist/storage/domains/projects/base.js.map +1 -1
  154. package/dist/storage/domains/work-items/base.d.ts +16 -2
  155. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  156. package/dist/storage/domains/work-items/base.js +40 -11
  157. package/dist/storage/domains/work-items/base.js.map +1 -1
  158. package/dist/tools/read-tools.d.ts +3 -1
  159. package/dist/tools/read-tools.d.ts.map +1 -1
  160. package/dist/tools/read-tools.js +3 -2
  161. package/dist/tools/read-tools.js.map +1 -1
  162. package/dist/tools/session-tools.d.ts +3 -1
  163. package/dist/tools/session-tools.d.ts.map +1 -1
  164. package/dist/tools/session-tools.js +2 -1
  165. package/dist/tools/session-tools.js.map +1 -1
  166. package/dist/tools/sessions-state-processor.d.ts +3 -1
  167. package/dist/tools/sessions-state-processor.d.ts.map +1 -1
  168. package/dist/tools/sessions-state-processor.js +2 -1
  169. package/dist/tools/sessions-state-processor.js.map +1 -1
  170. package/dist/tools/work-item-tools.d.ts +3 -1
  171. package/dist/tools/work-item-tools.d.ts.map +1 -1
  172. package/dist/tools/work-item-tools.js +3 -2
  173. package/dist/tools/work-item-tools.js.map +1 -1
  174. package/dist/workspace.d.ts +2 -0
  175. package/dist/workspace.d.ts.map +1 -1
  176. package/dist/workspace.js +14 -5
  177. package/dist/workspace.js.map +1 -1
  178. package/factory-skills/factory-plan/SKILL.md +1 -1
  179. package/factory-skills/factory-triage/SKILL.md +18 -7
  180. package/package.json +9 -9
@@ -7,7 +7,10 @@
7
7
  * every member reads the same one.
8
8
  */
9
9
  import { FactoryStorageDomain } from '@mastra/core/storage';
10
- import type { CollectionSchema } from '@mastra/core/storage';
10
+ import type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';
11
+ export declare class FactoryProjectNotFoundError extends Error {
12
+ constructor();
13
+ }
11
14
  export interface IntakeSelection {
12
15
  enabled: boolean;
13
16
  /** Provider-owned source ids; `null` means nothing is selected. */
@@ -28,15 +31,52 @@ export declare function mergeIntakeSelections(configs: readonly IntakeConfig[]):
28
31
  * ingested item should land. GitHub items are naturally scoped by their linked
29
32
  * repository; providers without that link (Linear) need this explicit binding
30
33
  * so viewing one project's board cannot materialize another project's items.
34
+ *
35
+ * Frozen: its unique index allows one binding per source, and indexes cannot be
36
+ * narrowed, so bindings moved to {@link INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA}. Rows
37
+ * are copied once at boot and never written again; the collection stays
38
+ * registered so an earlier build that is rolled back to still finds them.
31
39
  */
32
40
  export declare const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema;
41
+ /**
42
+ * Binds an intake source to a Factory project, one row per (source, Factory
43
+ * project). Most integrations keep one binding per source and rebinding moves
44
+ * it; integrations with `Intake.bindingLabelFilters` let several Factory
45
+ * projects share a source, each taking the issues carrying its own labels.
46
+ */
47
+ export declare const INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA: CollectionSchema;
48
+ /** One-time data migrations that have run, so none re-runs over rows users have changed since. */
49
+ export declare const INTAKE_DATA_MIGRATIONS_SCHEMA: CollectionSchema;
33
50
  export interface IntakeSourceBinding {
34
51
  integrationId: string;
35
52
  sourceId: string;
36
53
  factoryProjectId: string;
37
54
  /** Installed board the source feeds, or `null` for legacy source-type routing. */
38
55
  board: string | null;
56
+ /** Labels whose issues this binding takes; `null` takes every issue no other binding's labels claim. */
57
+ labels: string[] | null;
58
+ createdAt: Date;
39
59
  }
60
+ /** Another Factory project's binding that already takes some of the requested issues. */
61
+ export interface IntakeBindingConflict {
62
+ factoryProjectId: string;
63
+ /** The overlapping labels, or `null` when both bindings take all unclaimed issues. */
64
+ labels: string[] | null;
65
+ }
66
+ /**
67
+ * The slice of a shared source's issues `binding` takes, given every binding
68
+ * on that source (`siblings` may include `binding` itself). Label bindings beat
69
+ * the all-issues binding, and an issue carrying labels of two label bindings
70
+ * goes to the older one.
71
+ */
72
+ export declare function bindingLabelSlice(binding: Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>, siblings: readonly Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>[]): {
73
+ include: string[] | null;
74
+ exclude: string[];
75
+ };
76
+ /** Whether an issue carrying `labels` falls in `binding`'s slice of its source. */
77
+ export declare function bindingTakesIssue(binding: Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>, siblings: readonly Pick<IntakeSourceBinding, 'factoryProjectId' | 'labels' | 'createdAt'>[], labels: readonly string[]): boolean;
78
+ /** Trim and de-duplicate case-insensitively, keeping the first spelling the user typed. */
79
+ export declare function normalizeBindingLabels(labels: readonly string[]): string[];
40
80
  /**
41
81
  * Routes items carrying a label to an installed board, per Factory project.
42
82
  *
@@ -82,7 +122,7 @@ export declare class IntakeStorage extends FactoryStorageDomain {
82
122
  orgId: string;
83
123
  config: IntakeConfig;
84
124
  }): Promise<IntakeConfig>;
85
- /** Every source binding in the org, optionally narrowed to one integration. */
125
+ /** Every source binding in the org, optionally narrowed to one integration, oldest first. */
86
126
  listBindings({ orgId, integrationId, }: {
87
127
  orgId: string;
88
128
  integrationId?: string;
@@ -93,17 +133,40 @@ export declare class IntakeStorage extends FactoryStorageDomain {
93
133
  integrationId: string;
94
134
  factoryProjectId: string;
95
135
  }): Promise<string[]>;
96
- /** The binding for one source, or `null` when it is unbound. */
97
- getBinding({ orgId, integrationId, sourceId, }: {
136
+ /**
137
+ * One Factory project's binding of a source, or `null` when it has none.
138
+ * Without `factoryProjectId`, the source's oldest binding — for integrations
139
+ * that keep one binding per source.
140
+ */
141
+ getBinding({ orgId, integrationId, sourceId, factoryProjectId, }: {
98
142
  orgId: string;
99
143
  integrationId: string;
100
144
  sourceId: string;
145
+ factoryProjectId?: string;
101
146
  }): Promise<IntakeSourceBinding | null>;
102
- clearBinding({ orgId, integrationId, sourceId, }: {
147
+ /**
148
+ * Remove one Factory project's binding of a source, or — without
149
+ * `factoryProjectId` — every binding of it. Returns the (oldest) removed
150
+ * binding, or `null` when there was none.
151
+ */
152
+ clearBinding({ orgId, integrationId, sourceId, factoryProjectId, }: {
103
153
  orgId: string;
104
154
  integrationId: string;
105
155
  sourceId: string;
156
+ factoryProjectId?: string;
106
157
  }): Promise<IntakeSourceBinding | null>;
158
+ /**
159
+ * Release every source a Factory project was bound to, so the others can take
160
+ * them. Pass the transaction deleting the project, so no binding lands in between.
161
+ */
162
+ clearProjectBindings({ orgId, factoryProjectId }: {
163
+ orgId: string;
164
+ factoryProjectId: string;
165
+ }, ops?: FactoryStorageOps): Promise<void>;
166
+ /**
167
+ * Bind a source to one Factory project, moving it away from any other: the
168
+ * source keeps a single binding, which takes all of its issues.
169
+ */
107
170
  setBinding({ orgId, integrationId, sourceId, factoryProjectId, board, userId, }: {
108
171
  orgId: string;
109
172
  integrationId: string;
@@ -112,6 +175,24 @@ export declare class IntakeStorage extends FactoryStorageDomain {
112
175
  board?: string | null;
113
176
  userId?: string;
114
177
  }): Promise<void>;
178
+ /**
179
+ * Bind a source to one Factory project alongside other Factory projects'
180
+ * bindings of it, taking the issues carrying one of `labels` — or, with
181
+ * `labels: null`, every issue no label binding claims. Rejected with the
182
+ * overlapping bindings when another Factory project already takes some of
183
+ * those labels, or already takes all unclaimed issues.
184
+ */
185
+ setSharedBinding({ orgId, integrationId, sourceId, factoryProjectId, board, labels, userId, }: {
186
+ orgId: string;
187
+ integrationId: string;
188
+ sourceId: string;
189
+ factoryProjectId: string;
190
+ board?: string | null;
191
+ labels: string[] | null;
192
+ userId?: string;
193
+ }): Promise<{
194
+ conflicts: IntakeBindingConflict[];
195
+ }>;
115
196
  /** Atomically replace legacy provider source ids in both selection and routing state. */
116
197
  migrateSourceIds({ orgId, integrationId, migrations, }: {
117
198
  orgId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/intake/base.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,18 @@ import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage
8
8
  * source ids the organization selected. The selection feeds shared boards, so
9
9
  * every member reads the same one.
10
10
  */
11
+ var FactoryProjectNotFoundError = class extends Error {
12
+ constructor() {
13
+ super("Factory project not found for this organization.");
14
+ this.name = "FactoryProjectNotFoundError";
15
+ }
16
+ };
17
+ async function assertFactoryProject(ops, orgId, factoryProjectId) {
18
+ if (!await ops.findOne("factory_projects", {
19
+ org_id: orgId,
20
+ id: factoryProjectId
21
+ })) throw new FactoryProjectNotFoundError();
22
+ }
11
23
  const DEFAULT_INTAKE_CONFIG = {};
12
24
  const INTAKE_ORG_SETTINGS_SCHEMA = {
13
25
  name: "intake_org_settings",
@@ -63,6 +75,11 @@ function mergeIntakeSelections(configs) {
63
75
  * ingested item should land. GitHub items are naturally scoped by their linked
64
76
  * repository; providers without that link (Linear) need this explicit binding
65
77
  * so viewing one project's board cannot materialize another project's items.
78
+ *
79
+ * Frozen: its unique index allows one binding per source, and indexes cannot be
80
+ * narrowed, so bindings moved to {@link INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA}. Rows
81
+ * are copied once at boot and never written again; the collection stays
82
+ * registered so an earlier build that is rolled back to still finds them.
66
83
  */
67
84
  const INTAKE_SOURCE_BINDINGS_SCHEMA = {
68
85
  name: "intake_source_bindings",
@@ -98,6 +115,105 @@ const INTAKE_SOURCE_BINDINGS_SCHEMA = {
98
115
  }]
99
116
  };
100
117
  /**
118
+ * Binds an intake source to a Factory project, one row per (source, Factory
119
+ * project). Most integrations keep one binding per source and rebinding moves
120
+ * it; integrations with `Intake.bindingLabelFilters` let several Factory
121
+ * projects share a source, each taking the issues carrying its own labels.
122
+ */
123
+ const INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA = {
124
+ name: "intake_source_subscriptions",
125
+ columns: {
126
+ id: { type: "uuid-pk" },
127
+ org_id: { type: "text" },
128
+ integration_id: { type: "text" },
129
+ source_id: { type: "text" },
130
+ factory_project_id: { type: "text" },
131
+ /** Installed board the source feeds; `null` keeps the legacy source-type routing (issues → work, PRs → review). */
132
+ board: {
133
+ type: "text",
134
+ nullable: true
135
+ },
136
+ /** Labels whose issues this binding takes, as the user typed them; `null` takes every unclaimed issue. */
137
+ labels: {
138
+ type: "json",
139
+ nullable: true
140
+ },
141
+ created_by_user_id: {
142
+ type: "text",
143
+ nullable: true
144
+ },
145
+ created_at: { type: "timestamp" },
146
+ updated_at: { type: "timestamp" }
147
+ },
148
+ indexes: [{
149
+ name: "intake_source_subscriptions_project_idx",
150
+ columns: ["org_id", "factory_project_id"]
151
+ }],
152
+ uniqueIndexes: [{
153
+ name: "intake_source_subscriptions_org_integration_source_project_unique",
154
+ columns: [
155
+ "org_id",
156
+ "integration_id",
157
+ "source_id",
158
+ "factory_project_id"
159
+ ]
160
+ }]
161
+ };
162
+ /** One-time data migrations that have run, so none re-runs over rows users have changed since. */
163
+ const INTAKE_DATA_MIGRATIONS_SCHEMA = {
164
+ name: "intake_data_migrations",
165
+ columns: {
166
+ name: {
167
+ type: "text",
168
+ primaryKey: true
169
+ },
170
+ completed_at: { type: "timestamp" }
171
+ }
172
+ };
173
+ const COPY_LEGACY_BINDINGS_MIGRATION = "copy_intake_source_bindings_to_subscriptions";
174
+ /** Bindings on one source in precedence order: the oldest binding wins an issue two of them match. */
175
+ function inPrecedenceOrder(bindings) {
176
+ return [...bindings].sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime() || left.factoryProjectId.localeCompare(right.factoryProjectId));
177
+ }
178
+ /**
179
+ * The slice of a shared source's issues `binding` takes, given every binding
180
+ * on that source (`siblings` may include `binding` itself). Label bindings beat
181
+ * the all-issues binding, and an issue carrying labels of two label bindings
182
+ * goes to the older one.
183
+ */
184
+ function bindingLabelSlice(binding, siblings) {
185
+ const labelled = inPrecedenceOrder(siblings.filter((sibling) => sibling.labels !== null && sibling.factoryProjectId !== binding.factoryProjectId));
186
+ if (binding.labels === null) return {
187
+ include: null,
188
+ exclude: labelled.flatMap((sibling) => sibling.labels ?? [])
189
+ };
190
+ const older = labelled.filter((sibling) => inPrecedenceOrder([sibling, binding])[0] === sibling);
191
+ return {
192
+ include: binding.labels,
193
+ exclude: older.flatMap((sibling) => sibling.labels ?? [])
194
+ };
195
+ }
196
+ /** Whether an issue carrying `labels` falls in `binding`'s slice of its source. */
197
+ function bindingTakesIssue(binding, siblings, labels) {
198
+ const carried = new Set(labels.map(normalizeIntakeLabel));
199
+ const { include, exclude } = bindingLabelSlice(binding, siblings);
200
+ if (exclude.some((label) => carried.has(normalizeIntakeLabel(label)))) return false;
201
+ return include === null || include.some((label) => carried.has(normalizeIntakeLabel(label)));
202
+ }
203
+ /** Trim and de-duplicate case-insensitively, keeping the first spelling the user typed. */
204
+ function normalizeBindingLabels(labels) {
205
+ const seen = /* @__PURE__ */ new Set();
206
+ const kept = [];
207
+ for (const label of labels) {
208
+ const trimmed = label.trim();
209
+ const key = normalizeIntakeLabel(trimmed);
210
+ if (!key || seen.has(key)) continue;
211
+ seen.add(key);
212
+ kept.push(trimmed);
213
+ }
214
+ return kept;
215
+ }
216
+ /**
101
217
  * Routes items carrying a label to an installed board, per Factory project.
102
218
  *
103
219
  * Repository-scoped providers (GitHub) already know which project an item
@@ -163,9 +279,43 @@ function toIntakeSourceBinding(row) {
163
279
  integrationId: row.integration_id,
164
280
  sourceId: row.source_id,
165
281
  factoryProjectId: row.factory_project_id,
166
- board: row.board ?? null
282
+ board: row.board ?? null,
283
+ labels: row.labels ?? null,
284
+ createdAt: row.created_at
167
285
  };
168
286
  }
287
+ /** Overlap between a requested binding and the other Factory projects' bindings on the same source. */
288
+ function bindingConflicts(factoryProjectId, labels, rows) {
289
+ const requested = new Set(labels?.map(normalizeIntakeLabel));
290
+ return rows.flatMap((row) => {
291
+ if (row.factory_project_id === factoryProjectId) return [];
292
+ const theirs = row.labels ?? null;
293
+ if (labels === null || theirs === null) return labels === null && theirs === null ? [{
294
+ factoryProjectId: row.factory_project_id,
295
+ labels: null
296
+ }] : [];
297
+ const overlap = theirs.filter((label) => requested.has(normalizeIntakeLabel(label)));
298
+ return overlap.length > 0 ? [{
299
+ factoryProjectId: row.factory_project_id,
300
+ labels: overlap
301
+ }] : [];
302
+ });
303
+ }
304
+ /** Update one (source, Factory project) binding in place, keeping its age, or insert it. */
305
+ async function upsertBinding(ops, key, patch, userId) {
306
+ const now = /* @__PURE__ */ new Date();
307
+ if (await ops.updateMany("intake_source_subscriptions", key, {
308
+ ...patch,
309
+ updated_at: now
310
+ }) > 0) return;
311
+ await ops.insertOne("intake_source_subscriptions", {
312
+ ...key,
313
+ ...patch,
314
+ created_by_user_id: userId ?? null,
315
+ created_at: now,
316
+ updated_at: now
317
+ });
318
+ }
169
319
  var IntakeStorage = class extends FactoryStorageDomain {
170
320
  constructor() {
171
321
  super("intake");
@@ -175,9 +325,49 @@ var IntakeStorage = class extends FactoryStorageDomain {
175
325
  INTAKE_ORG_SETTINGS_SCHEMA,
176
326
  LEGACY_INTAKE_USER_SETTINGS_SCHEMA,
177
327
  INTAKE_SOURCE_BINDINGS_SCHEMA,
328
+ INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA,
329
+ INTAKE_DATA_MIGRATIONS_SCHEMA,
178
330
  INTAKE_LABEL_ROUTES_SCHEMA
179
331
  ]);
180
332
  await this.#foldLegacyUserSelections();
333
+ await this.#copyLegacyBindings();
334
+ }
335
+ /**
336
+ * Copy `intake_source_bindings` into `intake_source_subscriptions` as
337
+ * all-issues bindings, once. The marker and the copy commit together, so a
338
+ * boot racing another cannot re-copy a binding removed after the first
339
+ * boot finished.
340
+ */
341
+ async #copyLegacyBindings() {
342
+ try {
343
+ await this.storage.withTransaction(async (ops) => {
344
+ if (await ops.findOne("intake_data_migrations", { name: COPY_LEGACY_BINDINGS_MIGRATION })) return;
345
+ const legacyRows = await ops.findMany("intake_source_bindings", {});
346
+ for (const row of legacyRows) {
347
+ const key = {
348
+ org_id: row.org_id,
349
+ integration_id: row.integration_id,
350
+ source_id: row.source_id,
351
+ factory_project_id: row.factory_project_id
352
+ };
353
+ if (await ops.findOne("intake_source_subscriptions", key)) continue;
354
+ await ops.insertOne("intake_source_subscriptions", {
355
+ ...key,
356
+ board: row.board ?? null,
357
+ labels: null,
358
+ created_by_user_id: row.created_by_user_id ?? null,
359
+ created_at: row.created_at,
360
+ updated_at: row.updated_at
361
+ });
362
+ }
363
+ await ops.insertOne("intake_data_migrations", {
364
+ name: COPY_LEGACY_BINDINGS_MIGRATION,
365
+ completed_at: /* @__PURE__ */ new Date()
366
+ });
367
+ }, { isolationLevel: "serializable" });
368
+ } catch (error) {
369
+ if (!(error instanceof UniqueViolationError)) throw error;
370
+ }
181
371
  }
182
372
  async #foldLegacyUserSelections() {
183
373
  const legacyRows = await this.ops.findMany("intake_settings", {});
@@ -192,6 +382,8 @@ var IntakeStorage = class extends FactoryStorageDomain {
192
382
  await this.ops.deleteMany("intake_org_settings", {});
193
383
  await this.ops.deleteMany("intake_settings", {});
194
384
  await this.ops.deleteMany("intake_source_bindings", {});
385
+ await this.ops.deleteMany("intake_source_subscriptions", {});
386
+ await this.ops.deleteMany("intake_data_migrations", {});
195
387
  await this.ops.deleteMany("intake_label_routes", {});
196
388
  }
197
389
  get #db() {
@@ -243,69 +435,120 @@ var IntakeStorage = class extends FactoryStorageDomain {
243
435
  throw error;
244
436
  }
245
437
  }
246
- /** Every source binding in the org, optionally narrowed to one integration. */
438
+ /** Every source binding in the org, optionally narrowed to one integration, oldest first. */
247
439
  async listBindings({ orgId, integrationId }) {
248
- return (await this.#db.findMany("intake_source_bindings", {
440
+ return inPrecedenceOrder((await this.#db.findMany("intake_source_subscriptions", {
249
441
  org_id: orgId,
250
442
  ...integrationId ? { integration_id: integrationId } : {}
251
- })).map(toIntakeSourceBinding);
443
+ })).map(toIntakeSourceBinding));
252
444
  }
253
445
  /** Source ids bound to one Factory project. Empty means "nothing bound". */
254
446
  async listBoundSourceIds({ orgId, integrationId, factoryProjectId }) {
255
- return (await this.#db.findMany("intake_source_bindings", {
447
+ return (await this.#db.findMany("intake_source_subscriptions", {
256
448
  org_id: orgId,
257
449
  integration_id: integrationId,
258
450
  factory_project_id: factoryProjectId
259
451
  })).map((row) => row.source_id);
260
452
  }
261
- /** The binding for one source, or `null` when it is unbound. */
262
- async getBinding({ orgId, integrationId, sourceId }) {
263
- const row = await this.#db.findOne("intake_source_bindings", {
453
+ /**
454
+ * One Factory project's binding of a source, or `null` when it has none.
455
+ * Without `factoryProjectId`, the source's oldest binding — for integrations
456
+ * that keep one binding per source.
457
+ */
458
+ async getBinding({ orgId, integrationId, sourceId, factoryProjectId }) {
459
+ return inPrecedenceOrder((await this.#db.findMany("intake_source_subscriptions", {
264
460
  org_id: orgId,
265
461
  integration_id: integrationId,
266
- source_id: sourceId
267
- });
268
- return row ? toIntakeSourceBinding(row) : null;
462
+ source_id: sourceId,
463
+ ...factoryProjectId ? { factory_project_id: factoryProjectId } : {}
464
+ })).map(toIntakeSourceBinding))[0] ?? null;
269
465
  }
270
- async clearBinding({ orgId, integrationId, sourceId }) {
466
+ /**
467
+ * Remove one Factory project's binding of a source, or — without
468
+ * `factoryProjectId` — every binding of it. Returns the (oldest) removed
469
+ * binding, or `null` when there was none.
470
+ */
471
+ async clearBinding({ orgId, integrationId, sourceId, factoryProjectId }) {
271
472
  const where = {
272
473
  org_id: orgId,
273
474
  integration_id: integrationId,
274
- source_id: sourceId
475
+ source_id: sourceId,
476
+ ...factoryProjectId ? { factory_project_id: factoryProjectId } : {}
275
477
  };
276
478
  return this.storage.withTransaction(async (ops) => {
277
- const row = await ops.findOne("intake_source_bindings", where);
278
- if (!row) return null;
279
- await ops.deleteMany("intake_source_bindings", where);
280
- return toIntakeSourceBinding(row);
479
+ const rows = await ops.findMany("intake_source_subscriptions", where);
480
+ if (rows.length === 0) return null;
481
+ await ops.deleteMany("intake_source_subscriptions", where);
482
+ return inPrecedenceOrder(rows.map(toIntakeSourceBinding))[0];
281
483
  }, { isolationLevel: "serializable" });
282
484
  }
485
+ /**
486
+ * Release every source a Factory project was bound to, so the others can take
487
+ * them. Pass the transaction deleting the project, so no binding lands in between.
488
+ */
489
+ async clearProjectBindings({ orgId, factoryProjectId }, ops = this.#db) {
490
+ await ops.deleteMany("intake_source_subscriptions", {
491
+ org_id: orgId,
492
+ factory_project_id: factoryProjectId
493
+ });
494
+ }
495
+ /**
496
+ * Bind a source to one Factory project, moving it away from any other: the
497
+ * source keeps a single binding, which takes all of its issues.
498
+ */
283
499
  async setBinding({ orgId, integrationId, sourceId, factoryProjectId, board = null, userId }) {
284
- const where = {
500
+ const source = {
285
501
  org_id: orgId,
286
502
  integration_id: integrationId,
287
503
  source_id: sourceId
288
504
  };
289
- const now = /* @__PURE__ */ new Date();
290
- const patch = {
291
- factory_project_id: factoryProjectId,
292
- board,
293
- updated_at: now
505
+ await this.storage.withTransaction(async (ops) => {
506
+ await assertFactoryProject(ops, orgId, factoryProjectId);
507
+ const rows = await ops.findMany("intake_source_subscriptions", source);
508
+ for (const row of rows) {
509
+ if (row.factory_project_id === factoryProjectId) continue;
510
+ await ops.deleteMany("intake_source_subscriptions", {
511
+ ...source,
512
+ factory_project_id: row.factory_project_id
513
+ });
514
+ }
515
+ await upsertBinding(ops, {
516
+ ...source,
517
+ factory_project_id: factoryProjectId
518
+ }, {
519
+ board,
520
+ labels: null
521
+ }, userId);
522
+ }, { isolationLevel: "serializable" });
523
+ }
524
+ /**
525
+ * Bind a source to one Factory project alongside other Factory projects'
526
+ * bindings of it, taking the issues carrying one of `labels` — or, with
527
+ * `labels: null`, every issue no label binding claims. Rejected with the
528
+ * overlapping bindings when another Factory project already takes some of
529
+ * those labels, or already takes all unclaimed issues.
530
+ */
531
+ async setSharedBinding({ orgId, integrationId, sourceId, factoryProjectId, board = null, labels, userId }) {
532
+ const source = {
533
+ org_id: orgId,
534
+ integration_id: integrationId,
535
+ source_id: sourceId
294
536
  };
295
- if (await this.#db.updateMany("intake_source_bindings", where, patch) > 0) return;
296
- try {
297
- await this.#db.insertOne("intake_source_bindings", {
298
- ...where,
299
- factory_project_id: factoryProjectId,
537
+ const kept = labels === null ? null : normalizeBindingLabels(labels);
538
+ return this.storage.withTransaction(async (ops) => {
539
+ await assertFactoryProject(ops, orgId, factoryProjectId);
540
+ const rows = await ops.findMany("intake_source_subscriptions", source);
541
+ const conflicts = bindingConflicts(factoryProjectId, kept, rows);
542
+ if (conflicts.length > 0) return { conflicts };
543
+ await upsertBinding(ops, {
544
+ ...source,
545
+ factory_project_id: factoryProjectId
546
+ }, {
300
547
  board,
301
- created_by_user_id: userId ?? null,
302
- created_at: now,
303
- updated_at: now
304
- });
305
- } catch (error) {
306
- if (!(error instanceof UniqueViolationError)) throw error;
307
- await this.#db.updateMany("intake_source_bindings", where, patch);
308
- }
548
+ labels: kept
549
+ }, userId);
550
+ return { conflicts: [] };
551
+ }, { isolationLevel: "serializable" });
309
552
  }
310
553
  /** Atomically replace legacy provider source ids in both selection and routing state. */
311
554
  async migrateSourceIds({ orgId, integrationId, migrations }) {
@@ -314,17 +557,19 @@ var IntakeStorage = class extends FactoryStorageDomain {
314
557
  conflicts: []
315
558
  };
316
559
  return this.storage.withTransaction(async (ops) => {
317
- const bindings = await ops.findMany("intake_source_bindings", {
560
+ const bindings = await ops.findMany("intake_source_subscriptions", {
318
561
  org_id: orgId,
319
562
  integration_id: integrationId
320
563
  });
321
- const bindingBySource = new Map(bindings.map((binding) => [binding.source_id, binding]));
564
+ const bindingsBySource = /* @__PURE__ */ new Map();
565
+ for (const binding of bindings) bindingsBySource.set(binding.source_id, [...bindingsBySource.get(binding.source_id) ?? [], binding]);
566
+ const destination = (rows) => rows.map((row) => `${row.factory_project_id}\0${row.board ?? ""}\0${JSON.stringify(row.labels ?? null)}`).sort().join("\n");
322
567
  const grouped = /* @__PURE__ */ new Map();
323
568
  for (const migration of migrations) grouped.set(migration.to, [...grouped.get(migration.to) ?? [], migration]);
324
569
  const conflicts = [];
325
570
  for (const [to, group] of grouped) {
326
- const candidates = [bindingBySource.get(to), ...group.map(({ from }) => bindingBySource.get(from))].filter((binding) => binding !== void 0);
327
- if (new Set(candidates.map((binding) => `${binding.factory_project_id}\0${binding.board ?? ""}`)).size > 1) conflicts.push(...group);
571
+ const candidates = [bindingsBySource.get(to), ...group.map(({ from }) => bindingsBySource.get(from))].filter((rows) => rows !== void 0);
572
+ if (new Set(candidates.map(destination)).size > 1) conflicts.push(...group);
328
573
  }
329
574
  if (conflicts.length > 0) return {
330
575
  migrations: [],
@@ -332,32 +577,26 @@ var IntakeStorage = class extends FactoryStorageDomain {
332
577
  };
333
578
  const now = /* @__PURE__ */ new Date();
334
579
  for (const [to, group] of grouped) {
335
- const canonical = bindingBySource.get(to);
336
- const legacy = group.flatMap(({ from }) => {
337
- const binding = bindingBySource.get(from);
338
- return binding ? [{
339
- from,
340
- binding
341
- }] : [];
342
- });
343
- if (!canonical && legacy.length > 0) {
580
+ const legacy = group.filter(({ from }) => bindingsBySource.has(from));
581
+ const scope = {
582
+ org_id: orgId,
583
+ integration_id: integrationId
584
+ };
585
+ if (!bindingsBySource.has(to) && legacy.length > 0) {
344
586
  const [first, ...duplicates] = legacy;
345
- await ops.updateMany("intake_source_bindings", {
346
- org_id: orgId,
347
- integration_id: integrationId,
587
+ await ops.updateMany("intake_source_subscriptions", {
588
+ ...scope,
348
589
  source_id: first.from
349
590
  }, {
350
591
  source_id: to,
351
592
  updated_at: now
352
593
  });
353
- for (const duplicate of duplicates) await ops.deleteMany("intake_source_bindings", {
354
- org_id: orgId,
355
- integration_id: integrationId,
594
+ for (const duplicate of duplicates) await ops.deleteMany("intake_source_subscriptions", {
595
+ ...scope,
356
596
  source_id: duplicate.from
357
597
  });
358
- } else if (canonical) for (const entry of legacy) await ops.deleteMany("intake_source_bindings", {
359
- org_id: orgId,
360
- integration_id: integrationId,
598
+ } else for (const entry of legacy) await ops.deleteMany("intake_source_subscriptions", {
599
+ ...scope,
361
600
  source_id: entry.from
362
601
  });
363
602
  }
@@ -434,6 +673,6 @@ var IntakeStorage = class extends FactoryStorageDomain {
434
673
  }, { isolationLevel: "serializable" });
435
674
  }
436
675
  };
437
- export { DEFAULT_INTAKE_CONFIG, INTAKE_LABEL_ROUTES_SCHEMA, INTAKE_ORG_SETTINGS_SCHEMA, INTAKE_SOURCE_BINDINGS_SCHEMA, IntakeStorage, LEGACY_INTAKE_USER_SETTINGS_SCHEMA, mergeIntakeSelections, normalizeIntakeLabel, resolveIntakeLabelRoute };
676
+ export { DEFAULT_INTAKE_CONFIG, FactoryProjectNotFoundError, INTAKE_DATA_MIGRATIONS_SCHEMA, INTAKE_LABEL_ROUTES_SCHEMA, INTAKE_ORG_SETTINGS_SCHEMA, INTAKE_SOURCE_BINDINGS_SCHEMA, INTAKE_SOURCE_SUBSCRIPTIONS_SCHEMA, IntakeStorage, LEGACY_INTAKE_USER_SETTINGS_SCHEMA, bindingLabelSlice, bindingTakesIssue, mergeIntakeSelections, normalizeBindingLabels, normalizeIntakeLabel, resolveIntakeLabelRoute };
438
677
 
439
678
  //# sourceMappingURL=base.js.map