@mastra/factory 0.16.0-alpha.7 → 0.16.0-alpha.8

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 (65) hide show
  1. package/README.md +2 -2
  2. package/dist/boards/work.d.ts.map +1 -1
  3. package/dist/boards/work.js +18 -1
  4. package/dist/boards/work.js.map +1 -1
  5. package/dist/factory.d.ts +3 -2
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +3 -2
  8. package/dist/factory.js.map +1 -1
  9. package/dist/integrations/base.d.ts +2 -0
  10. package/dist/integrations/base.d.ts.map +1 -1
  11. package/dist/integrations/incidentio/agent-tools.d.ts +49 -0
  12. package/dist/integrations/incidentio/agent-tools.d.ts.map +1 -0
  13. package/dist/integrations/incidentio/agent-tools.js +90 -0
  14. package/dist/integrations/incidentio/agent-tools.js.map +1 -0
  15. package/dist/integrations/incidentio/claim.d.ts +8 -0
  16. package/dist/integrations/incidentio/claim.d.ts.map +1 -0
  17. package/dist/integrations/incidentio/claim.js +14 -0
  18. package/dist/integrations/incidentio/claim.js.map +1 -0
  19. package/dist/integrations/incidentio/default-rules.d.ts +46 -0
  20. package/dist/integrations/incidentio/default-rules.d.ts.map +1 -0
  21. package/dist/integrations/incidentio/default-rules.js +66 -0
  22. package/dist/integrations/incidentio/default-rules.js.map +1 -0
  23. package/dist/integrations/incidentio/integration.d.ts +48 -2
  24. package/dist/integrations/incidentio/integration.d.ts.map +1 -1
  25. package/dist/integrations/incidentio/integration.js +88 -2
  26. package/dist/integrations/incidentio/integration.js.map +1 -1
  27. package/dist/integrations/incidentio/issue-reconciler.d.ts +2 -0
  28. package/dist/integrations/incidentio/issue-reconciler.d.ts.map +1 -1
  29. package/dist/integrations/incidentio/issue-reconciler.js +38 -2
  30. package/dist/integrations/incidentio/issue-reconciler.js.map +1 -1
  31. package/dist/integrations/incidentio/routes.d.ts +71 -0
  32. package/dist/integrations/incidentio/routes.d.ts.map +1 -0
  33. package/dist/integrations/incidentio/routes.js +245 -0
  34. package/dist/integrations/incidentio/routes.js.map +1 -0
  35. package/dist/integrations/incidentio/rules.d.ts +55 -0
  36. package/dist/integrations/incidentio/rules.d.ts.map +1 -0
  37. package/dist/integrations/incidentio/rules.js +175 -0
  38. package/dist/integrations/incidentio/rules.js.map +1 -0
  39. package/dist/integrations/platform/connect/routes.d.ts.map +1 -1
  40. package/dist/integrations/platform/connect/routes.js +10 -4
  41. package/dist/integrations/platform/connect/routes.js.map +1 -1
  42. package/dist/integrations/platform/incidentio/integration.d.ts +61 -4
  43. package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
  44. package/dist/integrations/platform/incidentio/integration.js +371 -22
  45. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  46. package/dist/routes/oauth.js +2 -5
  47. package/dist/routes/oauth.js.map +1 -1
  48. package/dist/rules/dispatcher.d.ts.map +1 -1
  49. package/dist/rules/dispatcher.js +1 -1
  50. package/dist/rules/dispatcher.js.map +1 -1
  51. package/dist/rules/index.d.ts +2 -2
  52. package/dist/rules/index.d.ts.map +1 -1
  53. package/dist/rules/index.js +2 -2
  54. package/dist/rules/types.d.ts +30 -3
  55. package/dist/rules/types.d.ts.map +1 -1
  56. package/dist/rules/types.js +5 -1
  57. package/dist/rules/types.js.map +1 -1
  58. package/dist/rules/validation.d.ts.map +1 -1
  59. package/dist/rules/validation.js +1 -0
  60. package/dist/rules/validation.js.map +1 -1
  61. package/dist/work-item-branch.d.ts +1 -1
  62. package/dist/work-item-branch.d.ts.map +1 -1
  63. package/dist/work-item-branch.js +3 -2
  64. package/dist/work-item-branch.js.map +1 -1
  65. package/package.json +6 -6
@@ -1,10 +1,35 @@
1
1
  import { workItemPhaseSemantics } from "../../boards/semantics.js";
2
2
  import "../../boards/index.js";
3
3
  import { createIssueReconciler } from "../issue-reconciler.js";
4
+ import { IncidentioRules } from "./rules.js";
4
5
  //#region src/integrations/incidentio/issue-reconciler.ts
6
+ function issueToIngress(itemRef, issue) {
7
+ return {
8
+ id: itemRef,
9
+ identifier: issue.identifier,
10
+ title: issue.title,
11
+ url: issue.url,
12
+ state: issue.state ?? "",
13
+ stateType: issue.stateType ?? "",
14
+ priorityLabel: issue.priority ?? "",
15
+ assignee: issue.assignee ?? null,
16
+ author: issue.author ?? null,
17
+ incident: null,
18
+ labels: [...issue.labels ?? []],
19
+ createdAt: issue.createdAt,
20
+ updatedAt: issue.updatedAt
21
+ };
22
+ }
5
23
  function attachIncidentioIssueReconciler(incidentio, context) {
6
24
  if (!context.runtime || !incidentio.intake.resolveIntakeDispatch) return void 0;
7
25
  const boards = context.runtime.boards;
26
+ const rules = new IncidentioRules({
27
+ projects: context.storage.projects,
28
+ storage: context.runtime.workItems,
29
+ configVersion: context.runtime.configVersion,
30
+ boards,
31
+ incidentioRules: incidentio.rules
32
+ });
8
33
  return createIssueReconciler({
9
34
  integrationId: "incidentio",
10
35
  intake: incidentio.intake,
@@ -12,8 +37,9 @@ function attachIncidentioIssueReconciler(incidentio, context) {
12
37
  storage: context.runtime.workItems,
13
38
  isTerminal: (item) => workItemPhaseSemantics(boards, item)?.kind === "terminal",
14
39
  issueId: (item) => item.externalSource?.externalId,
15
- metadata: (_item, issue) => ({
40
+ metadata: (item, issue) => ({
16
41
  identifier: issue.identifier,
42
+ issueRef: item.externalSource?.externalId ?? issue.id,
17
43
  autoStartCandidate: issue.stateType === "unstarted" || issue.stateType === "started",
18
44
  incidentioItemType: issue.source === "Follow-up" ? "follow-up" : "incident",
19
45
  incidentioState: issue.state,
@@ -21,6 +47,7 @@ function attachIncidentioIssueReconciler(incidentio, context) {
21
47
  incidentioPriority: issue.priority,
22
48
  incidentioAssignee: issue.assignee,
23
49
  incidentioDescription: "description" in issue && typeof issue.description === "string" ? issue.description : null,
50
+ state: issue.state,
24
51
  stateType: issue.stateType,
25
52
  priority: issue.priority,
26
53
  source: issue.source,
@@ -29,8 +56,17 @@ function attachIncidentioIssueReconciler(incidentio, context) {
29
56
  creator: issue.author,
30
57
  author: issue.author,
31
58
  labels: issue.labels ?? [],
59
+ createdAt: issue.createdAt,
32
60
  updatedAt: issue.updatedAt
33
- })
61
+ }),
62
+ onClosed: async (item, issue, project) => {
63
+ await rules.ingest({
64
+ orgId: project.orgId,
65
+ userId: "factory-rule-dispatcher",
66
+ factoryProjectId: project.id,
67
+ issues: [issueToIngress(item.externalSource?.externalId ?? issue.id, issue)]
68
+ });
69
+ }
34
70
  });
35
71
  }
36
72
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/incidentio/issue-reconciler.ts"],"sourcesContent":["import { workItemPhaseSemantics } from '../../boards/index.js';\nimport type { Intake } from '../../capabilities/intake.js';\nimport type { IntegrationContext } from '../base.js';\nimport { createIssueReconciler } from '../issue-reconciler.js';\nimport type { IssueReconciler } from '../issue-reconciler.js';\n\nexport type IncidentioIssueReconciler = IssueReconciler;\n\nexport function attachIncidentioIssueReconciler(\n incidentio: { intake: Intake },\n context: IntegrationContext,\n): IncidentioIssueReconciler | undefined {\n if (!context.runtime || !incidentio.intake.resolveIntakeDispatch) return undefined;\n const boards = context.runtime.boards;\n\n return createIssueReconciler({\n integrationId: 'incidentio',\n intake: incidentio.intake,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n isTerminal: item => workItemPhaseSemantics(boards, item)?.kind === 'terminal',\n issueId: item => item.externalSource?.externalId,\n metadata: (_item, issue) => ({\n identifier: issue.identifier,\n autoStartCandidate: issue.stateType === 'unstarted' || issue.stateType === 'started',\n incidentioItemType: issue.source === 'Follow-up' ? 'follow-up' : 'incident',\n incidentioState: issue.state,\n incidentioStateType: issue.stateType,\n incidentioPriority: issue.priority,\n incidentioAssignee: issue.assignee,\n incidentioDescription: 'description' in issue && typeof issue.description === 'string' ? issue.description : null,\n stateType: issue.stateType,\n priority: issue.priority,\n source: issue.source,\n assignee: issue.assignee,\n assignees: issue.assignees ?? [],\n creator: issue.author,\n author: issue.author,\n labels: issue.labels ?? [],\n updatedAt: issue.updatedAt,\n }),\n });\n}\n"],"mappings":";;;;AAQA,SAAgB,gCACd,YACA,SACuC;CACvC,IAAI,CAAC,QAAQ,WAAW,CAAC,WAAW,OAAO,uBAAuB,OAAO,KAAA;CACzE,MAAM,SAAS,QAAQ,QAAQ;CAE/B,OAAO,sBAAsB;EAC3B,eAAe;EACf,QAAQ,WAAW;EACnB,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,aAAY,SAAQ,uBAAuB,QAAQ,IAAI,CAAC,EAAE,SAAS;EACnE,UAAS,SAAQ,KAAK,gBAAgB;EACtC,WAAW,OAAO,WAAW;GAC3B,YAAY,MAAM;GAClB,oBAAoB,MAAM,cAAc,eAAe,MAAM,cAAc;GAC3E,oBAAoB,MAAM,WAAW,cAAc,cAAc;GACjE,iBAAiB,MAAM;GACvB,qBAAqB,MAAM;GAC3B,oBAAoB,MAAM;GAC1B,oBAAoB,MAAM;GAC1B,uBAAuB,iBAAiB,SAAS,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc;GAC7G,WAAW,MAAM;GACjB,UAAU,MAAM;GAChB,QAAQ,MAAM;GACd,UAAU,MAAM;GAChB,WAAW,MAAM,aAAa,CAAC;GAC/B,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,QAAQ,MAAM,UAAU,CAAC;GACzB,WAAW,MAAM;EACnB;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"issue-reconciler.js","names":[],"sources":["../../../src/integrations/incidentio/issue-reconciler.ts"],"sourcesContent":["import { workItemPhaseSemantics } from '../../boards/index.js';\nimport type { Intake, IntakeIssue } from '../../capabilities/intake.js';\nimport type { IntegrationContext } from '../base.js';\nimport { createIssueReconciler } from '../issue-reconciler.js';\nimport type { IssueReconciler } from '../issue-reconciler.js';\nimport type { IncidentioEventRules } from './default-rules.js';\nimport { IncidentioRules } from './rules.js';\nimport type { IncidentioIssueIngress } from './rules.js';\n\nexport type IncidentioIssueReconciler = IssueReconciler;\n\nfunction issueToIngress(itemRef: string, issue: IntakeIssue): IncidentioIssueIngress {\n return {\n id: itemRef,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n state: issue.state ?? '',\n stateType: issue.stateType ?? '',\n priorityLabel: issue.priority ?? '',\n assignee: issue.assignee ?? null,\n author: issue.author ?? null,\n incident: null,\n labels: [...(issue.labels ?? [])],\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nexport function attachIncidentioIssueReconciler(\n incidentio: { intake: Intake; rules: IncidentioEventRules },\n context: IntegrationContext,\n): IncidentioIssueReconciler | undefined {\n if (!context.runtime || !incidentio.intake.resolveIntakeDispatch) return undefined;\n const boards = context.runtime.boards;\n\n const rules = new IncidentioRules({\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n configVersion: context.runtime.configVersion,\n boards,\n incidentioRules: incidentio.rules,\n });\n\n return createIssueReconciler({\n integrationId: 'incidentio',\n intake: incidentio.intake,\n projects: context.storage.projects,\n storage: context.runtime.workItems,\n isTerminal: item => workItemPhaseSemantics(boards, item)?.kind === 'terminal',\n issueId: item => item.externalSource?.externalId,\n metadata: (item, issue) => ({\n identifier: issue.identifier,\n issueRef: item.externalSource?.externalId ?? issue.id,\n autoStartCandidate: issue.stateType === 'unstarted' || issue.stateType === 'started',\n incidentioItemType: issue.source === 'Follow-up' ? 'follow-up' : 'incident',\n incidentioState: issue.state,\n incidentioStateType: issue.stateType,\n incidentioPriority: issue.priority,\n incidentioAssignee: issue.assignee,\n incidentioDescription: 'description' in issue && typeof issue.description === 'string' ? issue.description : null,\n state: issue.state,\n stateType: issue.stateType,\n priority: issue.priority,\n source: issue.source,\n assignee: issue.assignee,\n assignees: issue.assignees ?? [],\n creator: issue.author,\n author: issue.author,\n labels: issue.labels ?? [],\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n }),\n onClosed: async (item, issue, project) => {\n await rules.ingest({\n orgId: project.orgId,\n userId: 'factory-rule-dispatcher',\n factoryProjectId: project.id,\n issues: [issueToIngress(item.externalSource?.externalId ?? issue.id, issue)],\n });\n },\n });\n}\n"],"mappings":";;;;;AAWA,SAAS,eAAe,SAAiB,OAA4C;CACnF,OAAO;EACL,IAAI;EACJ,YAAY,MAAM;EAClB,OAAO,MAAM;EACb,KAAK,MAAM;EACX,OAAO,MAAM,SAAS;EACtB,WAAW,MAAM,aAAa;EAC9B,eAAe,MAAM,YAAY;EACjC,UAAU,MAAM,YAAY;EAC5B,QAAQ,MAAM,UAAU;EACxB,UAAU;EACV,QAAQ,CAAC,GAAI,MAAM,UAAU,CAAC,CAAE;EAChC,WAAW,MAAM;EACjB,WAAW,MAAM;CACnB;AACF;AAEA,SAAgB,gCACd,YACA,SACuC;CACvC,IAAI,CAAC,QAAQ,WAAW,CAAC,WAAW,OAAO,uBAAuB,OAAO,KAAA;CACzE,MAAM,SAAS,QAAQ,QAAQ;CAE/B,MAAM,QAAQ,IAAI,gBAAgB;EAChC,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,eAAe,QAAQ,QAAQ;EAC/B;EACA,iBAAiB,WAAW;CAC9B,CAAC;CAED,OAAO,sBAAsB;EAC3B,eAAe;EACf,QAAQ,WAAW;EACnB,UAAU,QAAQ,QAAQ;EAC1B,SAAS,QAAQ,QAAQ;EACzB,aAAY,SAAQ,uBAAuB,QAAQ,IAAI,CAAC,EAAE,SAAS;EACnE,UAAS,SAAQ,KAAK,gBAAgB;EACtC,WAAW,MAAM,WAAW;GAC1B,YAAY,MAAM;GAClB,UAAU,KAAK,gBAAgB,cAAc,MAAM;GACnD,oBAAoB,MAAM,cAAc,eAAe,MAAM,cAAc;GAC3E,oBAAoB,MAAM,WAAW,cAAc,cAAc;GACjE,iBAAiB,MAAM;GACvB,qBAAqB,MAAM;GAC3B,oBAAoB,MAAM;GAC1B,oBAAoB,MAAM;GAC1B,uBAAuB,iBAAiB,SAAS,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc;GAC7G,OAAO,MAAM;GACb,WAAW,MAAM;GACjB,UAAU,MAAM;GAChB,QAAQ,MAAM;GACd,UAAU,MAAM;GAChB,WAAW,MAAM,aAAa,CAAC;GAC/B,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,QAAQ,MAAM,UAAU,CAAC;GACzB,WAAW,MAAM;GACjB,WAAW,MAAM;EACnB;EACA,UAAU,OAAO,MAAM,OAAO,YAAY;GACxC,MAAM,MAAM,OAAO;IACjB,OAAO,QAAQ;IACf,QAAQ;IACR,kBAAkB,QAAQ;IAC1B,QAAQ,CAAC,eAAe,KAAK,gBAAgB,cAAc,MAAM,IAAI,KAAK,CAAC;GAC7E,CAAC;EACH;CACF,CAAC;AACH"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Mastra `apiRoutes` for the incident.io intake feature.
3
+ *
4
+ * Registered alongside the other `/web/*` routes, behind the host auth gate.
5
+ * Mirrors the Jira module: no connect/callback flow here — credentials are
6
+ * either deployment-global constructor config (direct) or Platform-managed
7
+ * connections discovered at runtime. Every route re-resolves the
8
+ * authenticated user from the request and scopes intake selections by the
9
+ * caller's org.
10
+ *
11
+ * When the feature is disabled (no auth, or no intake storage),
12
+ * `buildIncidentioRoutes` returns only `GET /web/incidentio/status`, which
13
+ * reports `enabled:false` so the SPA can cleanly hide all incident.io UI.
14
+ */
15
+ import type { ApiRoute } from '@mastra/core/server';
16
+ import type { Intake } from '../../capabilities/intake.js';
17
+ import type { RouteAuth } from '../../routes/route.js';
18
+ import type { IntakeStorage } from '../../storage/domains/intake/base.js';
19
+ import type { IncidentioRulesIngress } from './rules.js';
20
+ /**
21
+ * Non-secret diagnostic snapshot of every incident.io feature gate, mirroring
22
+ * the Jira diagnostics shape. Only booleans — never values.
23
+ */
24
+ export interface IncidentioFeatureDiagnostics {
25
+ incidentioConfigured: boolean;
26
+ factoryAuthEnabled: boolean;
27
+ }
28
+ export interface MountIncidentioRoutesOptions {
29
+ /**
30
+ * The integration's intake capability providing incident.io access. Required
31
+ * for everything beyond the disabled `status` route.
32
+ */
33
+ incidentio?: {
34
+ intake: Intake;
35
+ id: string;
36
+ };
37
+ /** Host auth seam. Intake selections are org-owned, so the feature is inert without it. */
38
+ auth: RouteAuth;
39
+ /**
40
+ * Cross-integration intake selection domain. Required for the issues route's
41
+ * project filter; when absent, only the disabled `status` route is served.
42
+ */
43
+ intake?: IntakeStorage;
44
+ /**
45
+ * Factory rules ingress for observed follow-ups. When present, a
46
+ * board-scoped listing also materializes new cards through the incident.io
47
+ * event rules — the same automatic intake Linear and Jira have.
48
+ */
49
+ ingestFactoryIssues?: (input: IncidentioRulesIngress) => Promise<unknown>;
50
+ }
51
+ /**
52
+ * Narrow the caller's selected incident.io sources to the ones that feed this
53
+ * Factory project. Routing is explicit: a source feeds this Factory only when
54
+ * its binding names both the Factory project and a board. Mirrors the Jira
55
+ * routes' scoping semantics — the generic binding storage is provider-neutral.
56
+ * Shared with the agent tools, which enforce the same source-level
57
+ * authorization before fetching item details.
58
+ */
59
+ export declare function scopeSourceIdsToProject({ intake, orgId, factoryProjectId, selectedIds, }: {
60
+ intake: IntakeStorage;
61
+ orgId: string;
62
+ factoryProjectId: string;
63
+ selectedIds: string[];
64
+ }): Promise<Record<string, string>>;
65
+ /**
66
+ * Build the incident.io routes as Mastra `apiRoutes`. When the feature is
67
+ * disabled, returns only the `status` route so the SPA can detect the
68
+ * disabled state.
69
+ */
70
+ export declare function buildIncidentioRoutes(options: MountIncidentioRoutesOptions): ApiRoute[];
71
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAazD;;;GAGG;AACH,MAAM,WAAW,4BAA4B;IAC3C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,2FAA2F;IAC3F,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,MAAM,EACN,KAAK,EACL,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CASlC;AAqED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,QAAQ,EAAE,CA8MvF"}
@@ -0,0 +1,245 @@
1
+ import { IncidentioApiError } from "./api.js";
2
+ import { registerApiRoute } from "@mastra/core/server";
3
+ //#region src/integrations/incidentio/routes.ts
4
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ const ITEM_REF_RE = /^incidentio:(?:follow-up|incident):[\w-]+$/;
6
+ const FOLLOW_UP_PREFIX = "incidentio:follow-up:";
7
+ /** Erase a route handler's path-parameterized context to a plain `Context`. */
8
+ function loose(c) {
9
+ return c;
10
+ }
11
+ /**
12
+ * Narrow the caller's selected incident.io sources to the ones that feed this
13
+ * Factory project. Routing is explicit: a source feeds this Factory only when
14
+ * its binding names both the Factory project and a board. Mirrors the Jira
15
+ * routes' scoping semantics — the generic binding storage is provider-neutral.
16
+ * Shared with the agent tools, which enforce the same source-level
17
+ * authorization before fetching item details.
18
+ */
19
+ async function scopeSourceIdsToProject({ intake, orgId, factoryProjectId, selectedIds }) {
20
+ const selected = new Set(selectedIds);
21
+ const intakeBoards = {};
22
+ for (const binding of await intake.listBindings({
23
+ orgId,
24
+ integrationId: "incidentio"
25
+ })) if (binding.factoryProjectId === factoryProjectId && binding.board && selected.has(binding.sourceId)) intakeBoards[binding.sourceId] = binding.board;
26
+ return intakeBoards;
27
+ }
28
+ /**
29
+ * Resolve the org-scoped tenant for an incident.io request. Intake selections
30
+ * are org-owned, so it requires both a signed-in user and an organization.
31
+ */
32
+ async function resolveOrgTenant(c, auth) {
33
+ await auth.ensureUser(c);
34
+ const tenant = auth.tenant(c);
35
+ if (!tenant) return { response: c.json({ error: "unauthorized" }, 401) };
36
+ if (!tenant.orgId) return { response: c.json({
37
+ error: "organization_required",
38
+ message: "incident.io intake requires an organization. Personal accounts cannot use incident.io intake."
39
+ }, 403) };
40
+ return { tenant: {
41
+ orgId: tenant.orgId,
42
+ userId: tenant.userId
43
+ } };
44
+ }
45
+ /**
46
+ * Validate an opaque pagination cursor from the query string. Cursors are
47
+ * server-issued base64url blobs, so anything outside a conservative
48
+ * charset/length is rejected rather than decoded.
49
+ */
50
+ function parseAfterCursor(raw) {
51
+ if (raw === void 0 || raw === "") return void 0;
52
+ if (raw.length > 2048 || !/^[\w+/=.:-]+$/.test(raw)) return null;
53
+ return raw;
54
+ }
55
+ /** Map an incident.io read failure to the API response for the SPA. */
56
+ function incidentioFetchError(c, err) {
57
+ if (err instanceof IncidentioApiError && (err.status === 401 || err.status === 403)) return c.json({
58
+ error: "incidentio_auth_failed",
59
+ message: "incident.io rejected the configured credentials. Reconnect the incident.io account."
60
+ }, 409);
61
+ return c.json({
62
+ error: "incidentio_fetch_failed",
63
+ message: err instanceof Error ? err.message : String(err)
64
+ }, 502);
65
+ }
66
+ /**
67
+ * Build the incident.io routes as Mastra `apiRoutes`. When the feature is
68
+ * disabled, returns only the `status` route so the SPA can detect the
69
+ * disabled state.
70
+ */
71
+ function buildIncidentioRoutes(options) {
72
+ const routes = [];
73
+ const { incidentio, auth, intake } = options;
74
+ const enabled = Boolean(incidentio) && auth.enabled();
75
+ const diagnostics = () => ({
76
+ incidentioConfigured: Boolean(incidentio),
77
+ factoryAuthEnabled: auth.enabled()
78
+ });
79
+ routes.push(registerApiRoute("/web/incidentio/status", {
80
+ method: "GET",
81
+ requiresAuth: false,
82
+ handler: async (c) => {
83
+ if (!enabled || !incidentio || !intake) return c.json({
84
+ enabled: false,
85
+ configured: Boolean(incidentio),
86
+ reason: "missing_config",
87
+ diagnostics: diagnostics()
88
+ });
89
+ await auth.ensureUser(loose(c));
90
+ const tenant = auth.tenant(loose(c));
91
+ if (!tenant) return c.json({
92
+ error: "unauthorized",
93
+ reason: "auth_required"
94
+ }, 401);
95
+ if (!tenant.orgId) return c.json({
96
+ enabled: true,
97
+ configured: true,
98
+ organizationRequired: true,
99
+ reason: "organization_required",
100
+ diagnostics: diagnostics()
101
+ });
102
+ return c.json({
103
+ enabled: true,
104
+ configured: true,
105
+ reason: "ready",
106
+ diagnostics: diagnostics()
107
+ });
108
+ }
109
+ }));
110
+ if (!enabled || !incidentio || !intake) return routes;
111
+ routes.push(registerApiRoute("/web/incidentio/issues", {
112
+ method: "GET",
113
+ requiresAuth: false,
114
+ handler: async (c) => {
115
+ const resolved = await resolveOrgTenant(loose(c), auth);
116
+ if ("response" in resolved) return resolved.response;
117
+ const after = parseAfterCursor(c.req.query("after"));
118
+ if (after === null) return c.json({ error: "invalid_cursor" }, 400);
119
+ const factoryProjectId = c.req.query("factoryProjectId");
120
+ if (factoryProjectId && !UUID_RE.test(factoryProjectId)) return c.json({ error: "invalid_factory_project_id" }, 400);
121
+ await intake.ensureReady();
122
+ const selection = (await intake.getConfig({
123
+ orgId: resolved.tenant.orgId,
124
+ integrationIds: ["incidentio"]
125
+ })).incidentio;
126
+ if (!selection.enabled) return c.json({
127
+ error: "incidentio_intake_disabled",
128
+ message: "incident.io intake is turned off in Settings."
129
+ }, 404);
130
+ const selectedIds = selection.sourceIds ?? [];
131
+ const intakeBoards = factoryProjectId ? await scopeSourceIdsToProject({
132
+ intake,
133
+ orgId: resolved.tenant.orgId,
134
+ factoryProjectId,
135
+ selectedIds
136
+ }) : null;
137
+ const sourceIds = intakeBoards ? Object.keys(intakeBoards) : selectedIds;
138
+ if (sourceIds.length === 0) return c.json({
139
+ issues: [],
140
+ nextCursor: null
141
+ });
142
+ try {
143
+ const page = await incidentio.intake.listItems({
144
+ orgId: resolved.tenant.orgId,
145
+ userId: resolved.tenant.userId,
146
+ sourceIds,
147
+ ...after ? { cursor: after } : {}
148
+ });
149
+ const issuePayload = [];
150
+ for (const item of page.items) {
151
+ const externalId = item.source.externalId;
152
+ if (!externalId.startsWith(FOLLOW_UP_PREFIX)) continue;
153
+ const metadata = item.metadata ?? {};
154
+ const identifier = typeof metadata.identifier === "string" ? metadata.identifier : externalId;
155
+ const titlePrefix = `${identifier}: `;
156
+ issuePayload.push({
157
+ id: externalId,
158
+ identifier,
159
+ title: item.title.startsWith(titlePrefix) ? item.title.slice(titlePrefix.length) : item.title,
160
+ url: item.source.url ?? "",
161
+ author: typeof metadata.author === "string" ? metadata.author : null,
162
+ state: item.status ?? "",
163
+ stateType: typeof metadata.stateType === "string" ? metadata.stateType : "",
164
+ priorityLabel: typeof metadata.priority === "string" ? metadata.priority : "",
165
+ assignee: item.assignee ?? null,
166
+ incident: typeof metadata.incidentioIncidentId === "string" ? metadata.incidentioIncidentId : null,
167
+ labels: item.labels ?? [],
168
+ createdAt: item.createdAt ?? "",
169
+ updatedAt: item.updatedAt ?? "",
170
+ sourceId: item.sourceId || null
171
+ });
172
+ }
173
+ if (factoryProjectId && intakeBoards && options.ingestFactoryIssues) await options.ingestFactoryIssues({
174
+ orgId: resolved.tenant.orgId,
175
+ userId: resolved.tenant.userId,
176
+ factoryProjectId,
177
+ issues: issuePayload,
178
+ intakeBoards
179
+ });
180
+ return c.json({
181
+ issues: issuePayload,
182
+ nextCursor: page.nextCursor
183
+ });
184
+ } catch (err) {
185
+ return incidentioFetchError(loose(c), err);
186
+ }
187
+ }
188
+ }));
189
+ routes.push(registerApiRoute("/web/incidentio/issues/detail", {
190
+ method: "GET",
191
+ requiresAuth: false,
192
+ handler: async (c) => {
193
+ const resolved = await resolveOrgTenant(loose(c), auth);
194
+ if ("response" in resolved) return resolved.response;
195
+ const factoryProjectId = c.req.query("factoryProjectId");
196
+ const issueRef = c.req.query("issueRef");
197
+ if (!factoryProjectId || !UUID_RE.test(factoryProjectId)) return c.json({ error: "invalid_factory_project_id" }, 400);
198
+ if (!issueRef || issueRef.length > 2048 || !ITEM_REF_RE.test(issueRef)) return c.json({ error: "invalid_issue_ref" }, 400);
199
+ await intake.ensureReady();
200
+ const selection = (await intake.getConfig({
201
+ orgId: resolved.tenant.orgId,
202
+ integrationIds: ["incidentio"]
203
+ })).incidentio;
204
+ if (!selection.enabled) return c.json({
205
+ error: "incidentio_intake_disabled",
206
+ message: "incident.io intake is turned off in Settings."
207
+ }, 404);
208
+ const intakeBoards = await scopeSourceIdsToProject({
209
+ intake,
210
+ orgId: resolved.tenant.orgId,
211
+ factoryProjectId,
212
+ selectedIds: selection.sourceIds ?? []
213
+ });
214
+ if (Object.keys(intakeBoards).length === 0) return c.json({ error: "issue_not_found" }, 404);
215
+ try {
216
+ const dispatch = await incidentio.intake.resolveIntakeDispatch?.({
217
+ orgId: resolved.tenant.orgId,
218
+ externalSource: {
219
+ type: "issue",
220
+ externalId: issueRef
221
+ }
222
+ });
223
+ if (!dispatch || !dispatch.sourceId || !(dispatch.sourceId in intakeBoards)) return c.json({ error: "issue_not_found" }, 404);
224
+ const issue = await incidentio.intake.getIssue({
225
+ connection: dispatch.connection,
226
+ issueId: dispatch.issueId
227
+ });
228
+ if (!issue) return c.json({ error: "issue_not_found" }, 404);
229
+ return c.json({ issue: {
230
+ identifier: issue.identifier,
231
+ title: issue.title,
232
+ url: issue.url,
233
+ description: issue.description ?? null
234
+ } });
235
+ } catch (err) {
236
+ return incidentioFetchError(loose(c), err);
237
+ }
238
+ }
239
+ }));
240
+ return routes;
241
+ }
242
+ //#endregion
243
+ export { buildIncidentioRoutes, scopeSourceIdsToProject };
244
+
245
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","names":[],"sources":["../../../src/integrations/incidentio/routes.ts"],"sourcesContent":["/**\n * Mastra `apiRoutes` for the incident.io intake feature.\n *\n * Registered alongside the other `/web/*` routes, behind the host auth gate.\n * Mirrors the Jira module: no connect/callback flow here — credentials are\n * either deployment-global constructor config (direct) or Platform-managed\n * connections discovered at runtime. Every route re-resolves the\n * authenticated user from the request and scopes intake selections by the\n * caller's org.\n *\n * When the feature is disabled (no auth, or no intake storage),\n * `buildIncidentioRoutes` returns only `GET /web/incidentio/status`, which\n * reports `enabled:false` so the SPA can cleanly hide all incident.io UI.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport type { Intake } from '../../capabilities/intake.js';\nimport type { RouteAuth } from '../../routes/route.js';\nimport type { IntakeStorage } from '../../storage/domains/intake/base.js';\nimport { IncidentioApiError } from './api.js';\nimport type { IncidentioRulesIngress } from './rules.js';\n\ntype RouteContext = Context;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst ITEM_REF_RE = /^incidentio:(?:follow-up|incident):[\\w-]+$/;\nconst FOLLOW_UP_PREFIX = 'incidentio:follow-up:';\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): RouteContext {\n return c as RouteContext;\n}\n\n/**\n * Non-secret diagnostic snapshot of every incident.io feature gate, mirroring\n * the Jira diagnostics shape. Only booleans — never values.\n */\nexport interface IncidentioFeatureDiagnostics {\n incidentioConfigured: boolean;\n factoryAuthEnabled: boolean;\n}\n\nexport interface MountIncidentioRoutesOptions {\n /**\n * The integration's intake capability providing incident.io access. Required\n * for everything beyond the disabled `status` route.\n */\n incidentio?: { intake: Intake; id: string };\n /** Host auth seam. Intake selections are org-owned, so the feature is inert without it. */\n auth: RouteAuth;\n /**\n * Cross-integration intake selection domain. Required for the issues route's\n * project filter; when absent, only the disabled `status` route is served.\n */\n intake?: IntakeStorage;\n /**\n * Factory rules ingress for observed follow-ups. When present, a\n * board-scoped listing also materializes new cards through the incident.io\n * event rules — the same automatic intake Linear and Jira have.\n */\n ingestFactoryIssues?: (input: IncidentioRulesIngress) => Promise<unknown>;\n}\n\n/**\n * Narrow the caller's selected incident.io sources to the ones that feed this\n * Factory project. Routing is explicit: a source feeds this Factory only when\n * its binding names both the Factory project and a board. Mirrors the Jira\n * routes' scoping semantics — the generic binding storage is provider-neutral.\n * Shared with the agent tools, which enforce the same source-level\n * authorization before fetching item details.\n */\nexport async function scopeSourceIdsToProject({\n intake,\n orgId,\n factoryProjectId,\n selectedIds,\n}: {\n intake: IntakeStorage;\n orgId: string;\n factoryProjectId: string;\n selectedIds: string[];\n}): Promise<Record<string, string>> {\n const selected = new Set(selectedIds);\n const intakeBoards: Record<string, string> = {};\n for (const binding of await intake.listBindings({ orgId, integrationId: 'incidentio' })) {\n if (binding.factoryProjectId === factoryProjectId && binding.board && selected.has(binding.sourceId)) {\n intakeBoards[binding.sourceId] = binding.board;\n }\n }\n return intakeBoards;\n}\n\n/**\n * Resolve the org-scoped tenant for an incident.io request. Intake selections\n * are org-owned, so it requires both a signed-in user and an organization.\n */\nasync function resolveOrgTenant(\n c: RouteContext,\n auth: RouteAuth,\n): Promise<{ tenant: { orgId: string; userId: string } } | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n {\n error: 'organization_required',\n message: 'incident.io intake requires an organization. Personal accounts cannot use incident.io intake.',\n },\n 403,\n ),\n };\n }\n return { tenant: { orgId: tenant.orgId, userId: tenant.userId } };\n}\n\n/**\n * Validate an opaque pagination cursor from the query string. Cursors are\n * server-issued base64url blobs, so anything outside a conservative\n * charset/length is rejected rather than decoded.\n */\nfunction parseAfterCursor(raw: string | undefined): string | undefined | null {\n if (raw === undefined || raw === '') return undefined;\n if (raw.length > 2_048 || !/^[\\w+/=.:-]+$/.test(raw)) return null;\n return raw;\n}\n\n/** Map an incident.io read failure to the API response for the SPA. */\nfunction incidentioFetchError(c: RouteContext, err: unknown) {\n if (err instanceof IncidentioApiError && (err.status === 401 || err.status === 403)) {\n return c.json(\n {\n error: 'incidentio_auth_failed',\n message: 'incident.io rejected the configured credentials. Reconnect the incident.io account.',\n },\n 409,\n );\n }\n return c.json({ error: 'incidentio_fetch_failed', message: err instanceof Error ? err.message : String(err) }, 502);\n}\n\ninterface IncidentioIssuePayload {\n id: string;\n identifier: string;\n title: string;\n url: string;\n author: string | null;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n incident: string | null;\n labels: string[];\n createdAt: string;\n updatedAt: string;\n sourceId: string | null;\n}\n\n/**\n * Build the incident.io routes as Mastra `apiRoutes`. When the feature is\n * disabled, returns only the `status` route so the SPA can detect the\n * disabled state.\n */\nexport function buildIncidentioRoutes(options: MountIncidentioRoutesOptions): ApiRoute[] {\n const routes: ApiRoute[] = [];\n const { incidentio, auth, intake } = options;\n const enabled = Boolean(incidentio) && auth.enabled();\n const diagnostics = (): IncidentioFeatureDiagnostics => ({\n incidentioConfigured: Boolean(incidentio),\n factoryAuthEnabled: auth.enabled(),\n });\n\n // The status route is always registered so the SPA can detect the disabled state.\n routes.push(\n registerApiRoute('/web/incidentio/status', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n if (!enabled || !incidentio || !intake) {\n return c.json({\n enabled: false,\n configured: Boolean(incidentio),\n reason: 'missing_config',\n diagnostics: diagnostics(),\n });\n }\n await auth.ensureUser(loose(c));\n const tenant = auth.tenant(loose(c));\n if (!tenant) return c.json({ error: 'unauthorized', reason: 'auth_required' }, 401);\n if (!tenant.orgId) {\n return c.json({\n enabled: true,\n configured: true,\n organizationRequired: true,\n reason: 'organization_required',\n diagnostics: diagnostics(),\n });\n }\n return c.json({ enabled: true, configured: true, reason: 'ready', diagnostics: diagnostics() });\n },\n }),\n );\n\n // Without the integration instance or the intake domain the feature can't\n // serve org-scoped data — serve only the disabled `status` route.\n if (!enabled || !incidentio || !intake) {\n return routes;\n }\n\n // ── List bound follow-ups (cursor-paged) ────────────────────────────────\n // Respects the caller's intake config: disabled incident.io intake 404s,\n // and only sources routed to the requested Factory project are listed.\n // Incidents never appear here — only follow-ups feed intake.\n routes.push(\n registerApiRoute('/web/incidentio/issues', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const after = parseAfterCursor(c.req.query('after'));\n if (after === null) return c.json({ error: 'invalid_cursor' }, 400);\n const factoryProjectId = c.req.query('factoryProjectId');\n if (factoryProjectId && !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['incidentio'] });\n const selection = config.incidentio!;\n if (!selection.enabled) {\n return c.json(\n { error: 'incidentio_intake_disabled', message: 'incident.io intake is turned off in Settings.' },\n 404,\n );\n }\n\n const selectedIds = selection.sourceIds ?? [];\n const intakeBoards = factoryProjectId\n ? await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds,\n })\n : null;\n const sourceIds = intakeBoards ? Object.keys(intakeBoards) : selectedIds;\n if (sourceIds.length === 0) {\n return c.json({ issues: [], nextCursor: null });\n }\n\n try {\n const page = await incidentio.intake.listItems({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n sourceIds,\n ...(after ? { cursor: after } : {}),\n });\n const issuePayload: IncidentioIssuePayload[] = [];\n for (const item of page.items) {\n const externalId = item.source.externalId;\n // Incidents stay out of intake even when a stale binding selects\n // the incidents source — only follow-ups are served.\n if (!externalId.startsWith(FOLLOW_UP_PREFIX)) continue;\n const metadata = item.metadata ?? {};\n const identifier = typeof metadata.identifier === 'string' ? metadata.identifier : externalId;\n const titlePrefix = `${identifier}: `;\n issuePayload.push({\n id: externalId,\n identifier,\n title: item.title.startsWith(titlePrefix) ? item.title.slice(titlePrefix.length) : item.title,\n url: item.source.url ?? '',\n author: typeof metadata.author === 'string' ? metadata.author : null,\n state: item.status ?? '',\n stateType: typeof metadata.stateType === 'string' ? metadata.stateType : '',\n priorityLabel: typeof metadata.priority === 'string' ? metadata.priority : '',\n assignee: item.assignee ?? null,\n incident: typeof metadata.incidentioIncidentId === 'string' ? metadata.incidentioIncidentId : null,\n labels: item.labels ?? [],\n createdAt: item.createdAt ?? '',\n updatedAt: item.updatedAt ?? '',\n sourceId: item.sourceId || null,\n });\n }\n if (factoryProjectId && intakeBoards && options.ingestFactoryIssues) {\n await options.ingestFactoryIssues({\n orgId: resolved.tenant.orgId,\n userId: resolved.tenant.userId,\n factoryProjectId,\n issues: issuePayload,\n intakeBoards,\n });\n }\n return c.json({ issues: issuePayload, nextCursor: page.nextCursor });\n } catch (err) {\n return incidentioFetchError(loose(c), err);\n }\n },\n }),\n );\n\n // ── Follow-up detail (description for the card panel) ───────────────────\n routes.push(\n registerApiRoute('/web/incidentio/issues/detail', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resolved = await resolveOrgTenant(loose(c), auth);\n if ('response' in resolved) return resolved.response;\n\n const factoryProjectId = c.req.query('factoryProjectId');\n const issueRef = c.req.query('issueRef');\n if (!factoryProjectId || !UUID_RE.test(factoryProjectId)) {\n return c.json({ error: 'invalid_factory_project_id' }, 400);\n }\n if (!issueRef || issueRef.length > 2_048 || !ITEM_REF_RE.test(issueRef)) {\n return c.json({ error: 'invalid_issue_ref' }, 400);\n }\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: resolved.tenant.orgId, integrationIds: ['incidentio'] });\n const selection = config.incidentio!;\n if (!selection.enabled) {\n return c.json(\n { error: 'incidentio_intake_disabled', message: 'incident.io intake is turned off in Settings.' },\n 404,\n );\n }\n const intakeBoards = await scopeSourceIdsToProject({\n intake,\n orgId: resolved.tenant.orgId,\n factoryProjectId,\n selectedIds: selection.sourceIds ?? [],\n });\n if (Object.keys(intakeBoards).length === 0) return c.json({ error: 'issue_not_found' }, 404);\n\n try {\n // The item must resolve through a source routed to this Factory —\n // an item from an unbound source reads exactly like one that\n // doesn't exist (same stance as the Jira detail route).\n const dispatch = await incidentio.intake.resolveIntakeDispatch?.({\n orgId: resolved.tenant.orgId,\n externalSource: { type: 'issue', externalId: issueRef },\n });\n if (!dispatch || !dispatch.sourceId || !(dispatch.sourceId in intakeBoards)) {\n return c.json({ error: 'issue_not_found' }, 404);\n }\n const issue = await incidentio.intake.getIssue({\n connection: dispatch.connection,\n issueId: dispatch.issueId,\n });\n if (!issue) return c.json({ error: 'issue_not_found' }, 404);\n return c.json({\n issue: {\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n description: issue.description ?? null,\n },\n });\n } catch (err) {\n return incidentioFetchError(loose(c), err);\n }\n },\n }),\n );\n\n return routes;\n}\n"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { BoardRegistry } from '../../boards/index.js';
2
+ import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';
3
+ import type { WorkItemsStorage } from '../../storage/domains/work-items/base.js';
4
+ import type { IntegrationContext } from '../base.js';
5
+ import type { IncidentioEventRules } from './default-rules.js';
6
+ export interface IncidentioIssueIngress {
7
+ /** Stable item reference — the prefixed incident.io follow-up id (`incidentio:follow-up:<ulid>`). */
8
+ id: string;
9
+ identifier: string;
10
+ title: string;
11
+ url: string;
12
+ state: string;
13
+ stateType: string;
14
+ priorityLabel: string;
15
+ assignee: string | null;
16
+ /** Display name of the follow-up creator, when available. */
17
+ author: string | null;
18
+ /** Reference of the incident this follow-up belongs to, when available. */
19
+ incident: string | null;
20
+ labels: string[];
21
+ createdAt: string;
22
+ updatedAt: string;
23
+ /** incident.io source the follow-up was read from; resolves the bound board via `intakeBoards`. */
24
+ sourceId?: string | null;
25
+ }
26
+ export interface IncidentioRulesOptions {
27
+ projects: Pick<FactoryProjectsStorage, 'get'>;
28
+ storage: WorkItemsStorage;
29
+ configVersion: string;
30
+ boards: BoardRegistry;
31
+ incidentioRules: IncidentioEventRules;
32
+ }
33
+ export interface IncidentioRulesIngress {
34
+ orgId: string;
35
+ userId: string;
36
+ factoryProjectId: string;
37
+ issues: IncidentioIssueIngress[];
38
+ /** Board id per bound incident.io source id, from the project's intake bindings. */
39
+ intakeBoards?: Readonly<Record<string, string>>;
40
+ }
41
+ type IngressStatus = 'committed' | 'replayed' | 'missing';
42
+ export declare class IncidentioRules {
43
+ #private;
44
+ private readonly options;
45
+ constructor(options: IncidentioRulesOptions);
46
+ ingest(input: IncidentioRulesIngress): Promise<{
47
+ status: IngressStatus;
48
+ ingested: number;
49
+ }>;
50
+ }
51
+ export declare function attachIncidentioRules(incidentio: {
52
+ readonly rules: IncidentioEventRules;
53
+ }, context: IntegrationContext): ((input: IncidentioRulesIngress) => Promise<unknown>) | undefined;
54
+ export {};
55
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/integrations/incidentio/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAe,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC9F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAkB/D,MAAM,WAAW,sBAAsB;IACrC,qGAAqG;IACrG,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,EAAE,gBAAgB,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,EAAE,oBAAoB,CAAC;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,oFAAoF;IACpF,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,KAAK,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAE1D,qBAAa,eAAe;;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,sBAAsB;IAEtD,MAAM,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAqIlG;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,EACpD,OAAO,EAAE,kBAAkB,GAC1B,CAAC,CAAC,KAAK,EAAE,sBAAsB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,CAUnE"}