@shipfox/api-triggers 11.0.0 → 12.0.0

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.
@@ -1,6 +1,6 @@
1
1
  $ shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js
2
- Successfully compiled: 58 files with swc (490.65ms)
3
- 2026-07-28T22:45:34.093Z [INFO] asset workflow-bundle-69915b80080285c7230d.js 3.17 MiB [emitted] [immutable] (name: main)
2
+ Successfully compiled: 58 files with swc (406.13ms)
3
+ 2026-08-04T08:17:12.242Z [INFO] asset workflow-bundle-69915b80080285c7230d.js 3.17 MiB [emitted] [immutable] (name: main)
4
4
  orphan modules 33.5 KiB [orphan] 21 modules
5
5
  runtime modules 1.13 KiB 5 modules
6
6
  modules by path ../../../node_modules/.pnpm/ 970 KiB 196 modules
@@ -13,6 +13,6 @@ optional modules 30 bytes [optional]
13
13
  __temporal_custom_payload_converter (ignored) 15 bytes [optional] [built] [code generated]
14
14
  __temporal_custom_failure_converter (ignored) 15 bytes [optional] [built] [code generated]
15
15
  ../../shared/node/temporal/dist/workflow-error-interceptor.js 1.21 KiB [built] [code generated]
16
- webpack 5.107.2 compiled successfully in 7195 ms
17
- 2026-07-28T22:45:34.114Z [INFO] Workflow bundle created { size: '3.17MB' }
16
+ webpack 5.107.2 compiled successfully in 4485 ms
17
+ 2026-08-04T08:17:12.280Z [INFO] Workflow bundle created { size: '3.17MB' }
18
18
  /home/runner/work/shipfox/shipfox/libs/api/triggers/dist/temporal/workflows/index.js: 3329015 bytes
package/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @shipfox/api-triggers
2
2
 
3
+ ## 12.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - f78740d: Remove Unicode dash punctuation from package prose and source comments.
8
+ - dea1ffd: Expose normalized repository references on listening execution events.
9
+ - 4d246d4: Align predicate validation and runtime evaluation with field-specific context contracts.
10
+ - cb0abfa: Expose the normalized trigger project, repository, ref, and commit in workflow context.
11
+ - Updated dependencies [ee2ce67]
12
+ - Updated dependencies [7c4116e]
13
+ - Updated dependencies [5d2c9cf]
14
+ - Updated dependencies [e95fdf4]
15
+ - Updated dependencies [3d91d1d]
16
+ - Updated dependencies [f7939c7]
17
+ - Updated dependencies [89f2c18]
18
+ - Updated dependencies [045895c]
19
+ - Updated dependencies [f78740d]
20
+ - Updated dependencies [9e1d599]
21
+ - Updated dependencies [dea1ffd]
22
+ - Updated dependencies [adf07e7]
23
+ - Updated dependencies [3f781ee]
24
+ - Updated dependencies [9fdd5e4]
25
+ - Updated dependencies [285fff2]
26
+ - Updated dependencies [e44a279]
27
+ - Updated dependencies [4d246d4]
28
+ - Updated dependencies [f13e8bb]
29
+ - Updated dependencies [4444079]
30
+ - Updated dependencies [869a792]
31
+ - Updated dependencies [54c820e]
32
+ - Updated dependencies [35a42bd]
33
+ - Updated dependencies [d77baaa]
34
+ - Updated dependencies [41d558c]
35
+ - Updated dependencies [032d316]
36
+ - Updated dependencies [c2a8e54]
37
+ - Updated dependencies [54c820e]
38
+ - Updated dependencies [cb0abfa]
39
+ - Updated dependencies [ee2ce67]
40
+ - Updated dependencies [7f90b0c]
41
+ - @shipfox/workflow-document@3.0.0
42
+ - @shipfox/api-definitions-dto@12.0.0
43
+ - @shipfox/api-workflows-dto@12.0.0
44
+ - @shipfox/expression@2.0.0
45
+ - @shipfox/inter-module@0.2.3
46
+ - @shipfox/node-fastify@0.4.1
47
+ - @shipfox/node-module@1.0.5
48
+ - @shipfox/api-integration-core-dto@12.0.0
49
+ - @shipfox/node-postgres@0.5.0
50
+ - @shipfox/api-auth-context@12.0.0
51
+ - @shipfox/node-drizzle@0.3.5
52
+ - @shipfox/node-outbox@0.2.6
53
+
3
54
  ## 11.0.0
4
55
 
5
56
  ### Major Changes
package/README.md CHANGED
@@ -162,14 +162,14 @@ unique per `(workflow_definition_id, name)`.
162
162
 
163
163
  ### Words we do not use
164
164
 
165
- - `provider` reserved for the integration module's identity
165
+ - `provider`: reserved for the integration module's identity
166
166
  (`integrations_connections.provider`). Trigger code says `source`.
167
- - `eventType` / `type` replaced by `event`. The bare word `type` would
167
+ - `eventType` / `type`: replaced by `event`. The bare word `type` would
168
168
  collide with TypeScript discriminators and with the `IntegrationProvider`
169
169
  capability `type` field.
170
- - `triggerContext` the runtime payload on a run is called
170
+ - `triggerContext`: the runtime payload on a run is called
171
171
  `triggerPayload`.
172
- - `kind` every trigger is identified by `(source, event)`. There is no
172
+ - `kind`: every trigger is identified by `(source, event)`. There is no
173
173
  separate axis.
174
174
 
175
175
  ## Architecture
@@ -205,17 +205,17 @@ or POST /workflow-definitions/:definitionId/fire-manual → look up manual subsc
205
205
  └─────────────────────────────────────────────────────┘
206
206
  ```
207
207
 
208
- ### Layer 1 workflow definition (source of truth)
208
+ ### Layer 1: workflow definition (source of truth)
209
209
 
210
210
  The YAML `triggers` map lives inside `workflow_definitions.definition`
211
211
  (JSONB owned by the definitions module). That is the only place trigger
212
212
  declarations live in raw form.
213
213
 
214
- ### Layer 2 projection (queryable)
214
+ ### Layer 2: projection (queryable)
215
215
 
216
216
  `triggers_subscriptions` is rebuilt from `DEFINITION_RESOLVED` events,
217
217
  which carry the parsed `triggers` map. The triggers module never reads the
218
- definitions table the event is the contract.
218
+ definitions table: the event is the contract.
219
219
 
220
220
  Cron triggers also project into `triggers_cron_schedules`, keyed by
221
221
  `subscription_id`. That table stores the resolved cron expression, timezone,
@@ -223,15 +223,15 @@ next fire time, and last fire time used by the cron firing engine.
223
223
 
224
224
  Indexes:
225
225
 
226
- - `(workflow_definition_id, name)` unique. One row per YAML trigger.
227
- - `(workspace_id, source, event)` the hot path for matching incoming
226
+ - `(workflow_definition_id, name)`: unique. One row per YAML trigger.
227
+ - `(workspace_id, source, event)`: the hot path for matching incoming
228
228
  integration events at workspace scope.
229
- - `(workflow_definition_id)` used to clean up the projection on
229
+ - `(workflow_definition_id)`: used to clean up the projection on
230
230
  `DEFINITION_DELETED`.
231
- - `triggers_cron_schedules.next_fire_at` used to drain due cron schedules
231
+ - `triggers_cron_schedules.next_fire_at`: used to drain due cron schedules
232
232
  in next-fire order.
233
233
 
234
- ### Layer 3 run history (immutable)
234
+ ### Layer 3: run history (immutable)
235
235
 
236
236
  `workflow_runs.trigger_source` and `trigger_event` are indexed text
237
237
  columns. `trigger_payload` is a JSONB column typed by `TriggerPayload`:
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAKpE,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,mBAAmB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAKrC;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACjB;IAAC,IAAI,EAAE,UAAU,CAAA;CAAC,GAClB;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE3C,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,sBAAsB,CAwB/F;AAED,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,2BAA2B,GAClC,uBAAuB,CAUzB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAKpE,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,mBAAmB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAKrC;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACjB;IAAC,IAAI,EAAE,UAAU,CAAA;CAAC,GAClB;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC;AAE3C,MAAM,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE7D,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,sBAAsB,CAwB/F;AAED,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,2BAA2B,GAClC,uBAAuB,CAUzB"}
@@ -1,4 +1,4 @@
1
- import { createWorkflowExpression, evaluateWorkflowPredicateFailClosed, InvalidWorkflowExpressionError } from '@shipfox/expression';
1
+ import { createWorkflowExpression, evaluateWorkflowPredicateFailClosed, InvalidWorkflowExpressionError, projectWorkflowPredicateContext } from '@shipfox/expression';
2
2
  // Narrow the jsonb projection at the read boundary: the parser writes the right shapes,
3
3
  // but the column is unconstrained and these values reach run inputs.
4
4
  export function readConfigInputs(subscription) {
@@ -48,13 +48,13 @@ export function evaluateStoredFilter(params) {
48
48
  export function evaluateTriggerFilter(params) {
49
49
  return evaluateStoredFilter({
50
50
  value: params.subscription.config.filter,
51
- context: {
51
+ context: projectWorkflowPredicateContext('trigger.filter', {
52
52
  event: params.payload,
53
53
  trigger: {
54
54
  source: params.source,
55
55
  event: params.event
56
56
  }
57
- },
57
+ }),
58
58
  invalidReason: 'Trigger subscription filter must be a non-empty string when set',
59
59
  evaluationFailedReason: 'Trigger filter evaluation failed'
60
60
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import {\n createWorkflowExpression,\n evaluateWorkflowPredicateFailClosed,\n InvalidWorkflowExpressionError,\n type WorkflowExpression,\n} from '@shipfox/expression';\nimport type {TriggerSubscription} from './entities/subscription.js';\n\n// Narrow the jsonb projection at the read boundary: the parser writes the right shapes,\n// but the column is unconstrained and these values reach run inputs.\n\nexport function readConfigInputs(\n subscription: TriggerSubscription,\n): Record<string, unknown> | undefined {\n const value = subscription.config.with;\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\nexport type TriggerFilterEvaluation =\n | {kind: 'matched'}\n | {kind: 'filtered'}\n | {kind: 'filter-error'; reason: string};\n\nexport type StoredFilterEvaluation = TriggerFilterEvaluation;\n\nexport interface EvaluateStoredFilterParams {\n value: unknown;\n context: Record<string, unknown>;\n invalidReason: string;\n evaluationFailedReason: string;\n}\n\nexport function evaluateStoredFilter(params: EvaluateStoredFilterParams): StoredFilterEvaluation {\n const {value} = params;\n if (value === null || value === undefined) return {kind: 'matched'};\n if (typeof value !== 'string' || value.trim() === '') {\n return {\n kind: 'filter-error',\n reason: params.invalidReason,\n };\n }\n\n let expression: WorkflowExpression;\n try {\n expression = createWorkflowExpression({source: value, check: {mode: 'syntax'}});\n } catch (error) {\n return {kind: 'filter-error', reason: reasonFrom(error)};\n }\n\n const result = evaluateWorkflowPredicateFailClosed(expression, params.context);\n\n if (result.evaluationFailed) {\n return {kind: 'filter-error', reason: params.evaluationFailedReason};\n }\n\n return result.value ? {kind: 'matched'} : {kind: 'filtered'};\n}\n\nexport interface EvaluateTriggerFilterParams {\n subscription: TriggerSubscription;\n source: string;\n event: string;\n payload: unknown;\n}\n\nexport function evaluateTriggerFilter(\n params: EvaluateTriggerFilterParams,\n): TriggerFilterEvaluation {\n return evaluateStoredFilter({\n value: params.subscription.config.filter,\n context: {\n event: params.payload,\n trigger: {source: params.source, event: params.event},\n },\n invalidReason: 'Trigger subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Trigger filter evaluation failed',\n });\n}\n\nfunction reasonFrom(error: unknown): string {\n if (error instanceof InvalidWorkflowExpressionError) return error.reason;\n if (error instanceof Error) return error.message;\n return String(error);\n}\n"],"names":["createWorkflowExpression","evaluateWorkflowPredicateFailClosed","InvalidWorkflowExpressionError","readConfigInputs","subscription","value","config","with","undefined","Array","isArray","evaluateStoredFilter","params","kind","trim","reason","invalidReason","expression","source","check","mode","error","reasonFrom","result","context","evaluationFailed","evaluationFailedReason","evaluateTriggerFilter","filter","event","payload","trigger","Error","message","String"],"mappings":"AAAA,SACEA,wBAAwB,EACxBC,mCAAmC,EACnCC,8BAA8B,QAEzB,sBAAsB;AAG7B,wFAAwF;AACxF,qEAAqE;AAErE,OAAO,SAASC,iBACdC,YAAiC;IAEjC,MAAMC,QAAQD,aAAaE,MAAM,CAACC,IAAI;IACtC,IAAIF,UAAU,QAAQA,UAAUG,WAAW,OAAOA;IAClD,IAAI,OAAOH,UAAU,YAAYI,MAAMC,OAAO,CAACL,QAAQ,OAAOG;IAC9D,OAAOH;AACT;AAgBA,OAAO,SAASM,qBAAqBC,MAAkC;IACrE,MAAM,EAACP,KAAK,EAAC,GAAGO;IAChB,IAAIP,UAAU,QAAQA,UAAUG,WAAW,OAAO;QAACK,MAAM;IAAS;IAClE,IAAI,OAAOR,UAAU,YAAYA,MAAMS,IAAI,OAAO,IAAI;QACpD,OAAO;YACLD,MAAM;YACNE,QAAQH,OAAOI,aAAa;QAC9B;IACF;IAEA,IAAIC;IACJ,IAAI;QACFA,aAAajB,yBAAyB;YAACkB,QAAQb;YAAOc,OAAO;gBAACC,MAAM;YAAQ;QAAC;IAC/E,EAAE,OAAOC,OAAO;QACd,OAAO;YAACR,MAAM;YAAgBE,QAAQO,WAAWD;QAAM;IACzD;IAEA,MAAME,SAAStB,oCAAoCgB,YAAYL,OAAOY,OAAO;IAE7E,IAAID,OAAOE,gBAAgB,EAAE;QAC3B,OAAO;YAACZ,MAAM;YAAgBE,QAAQH,OAAOc,sBAAsB;QAAA;IACrE;IAEA,OAAOH,OAAOlB,KAAK,GAAG;QAACQ,MAAM;IAAS,IAAI;QAACA,MAAM;IAAU;AAC7D;AASA,OAAO,SAASc,sBACdf,MAAmC;IAEnC,OAAOD,qBAAqB;QAC1BN,OAAOO,OAAOR,YAAY,CAACE,MAAM,CAACsB,MAAM;QACxCJ,SAAS;YACPK,OAAOjB,OAAOkB,OAAO;YACrBC,SAAS;gBAACb,QAAQN,OAAOM,MAAM;gBAAEW,OAAOjB,OAAOiB,KAAK;YAAA;QACtD;QACAb,eAAe;QACfU,wBAAwB;IAC1B;AACF;AAEA,SAASJ,WAAWD,KAAc;IAChC,IAAIA,iBAAiBnB,gCAAgC,OAAOmB,MAAMN,MAAM;IACxE,IAAIM,iBAAiBW,OAAO,OAAOX,MAAMY,OAAO;IAChD,OAAOC,OAAOb;AAChB"}
1
+ {"version":3,"sources":["../../src/core/config.ts"],"sourcesContent":["import {\n createWorkflowExpression,\n evaluateWorkflowPredicateFailClosed,\n InvalidWorkflowExpressionError,\n projectWorkflowPredicateContext,\n type WorkflowExpression,\n} from '@shipfox/expression';\nimport type {TriggerSubscription} from './entities/subscription.js';\n\n// Narrow the jsonb projection at the read boundary: the parser writes the right shapes,\n// but the column is unconstrained and these values reach run inputs.\n\nexport function readConfigInputs(\n subscription: TriggerSubscription,\n): Record<string, unknown> | undefined {\n const value = subscription.config.with;\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\nexport type TriggerFilterEvaluation =\n | {kind: 'matched'}\n | {kind: 'filtered'}\n | {kind: 'filter-error'; reason: string};\n\nexport type StoredFilterEvaluation = TriggerFilterEvaluation;\n\nexport interface EvaluateStoredFilterParams {\n value: unknown;\n context: Record<string, unknown>;\n invalidReason: string;\n evaluationFailedReason: string;\n}\n\nexport function evaluateStoredFilter(params: EvaluateStoredFilterParams): StoredFilterEvaluation {\n const {value} = params;\n if (value === null || value === undefined) return {kind: 'matched'};\n if (typeof value !== 'string' || value.trim() === '') {\n return {\n kind: 'filter-error',\n reason: params.invalidReason,\n };\n }\n\n let expression: WorkflowExpression;\n try {\n expression = createWorkflowExpression({source: value, check: {mode: 'syntax'}});\n } catch (error) {\n return {kind: 'filter-error', reason: reasonFrom(error)};\n }\n\n const result = evaluateWorkflowPredicateFailClosed(expression, params.context);\n\n if (result.evaluationFailed) {\n return {kind: 'filter-error', reason: params.evaluationFailedReason};\n }\n\n return result.value ? {kind: 'matched'} : {kind: 'filtered'};\n}\n\nexport interface EvaluateTriggerFilterParams {\n subscription: TriggerSubscription;\n source: string;\n event: string;\n payload: unknown;\n}\n\nexport function evaluateTriggerFilter(\n params: EvaluateTriggerFilterParams,\n): TriggerFilterEvaluation {\n return evaluateStoredFilter({\n value: params.subscription.config.filter,\n context: projectWorkflowPredicateContext('trigger.filter', {\n event: params.payload,\n trigger: {source: params.source, event: params.event},\n }),\n invalidReason: 'Trigger subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Trigger filter evaluation failed',\n });\n}\n\nfunction reasonFrom(error: unknown): string {\n if (error instanceof InvalidWorkflowExpressionError) return error.reason;\n if (error instanceof Error) return error.message;\n return String(error);\n}\n"],"names":["createWorkflowExpression","evaluateWorkflowPredicateFailClosed","InvalidWorkflowExpressionError","projectWorkflowPredicateContext","readConfigInputs","subscription","value","config","with","undefined","Array","isArray","evaluateStoredFilter","params","kind","trim","reason","invalidReason","expression","source","check","mode","error","reasonFrom","result","context","evaluationFailed","evaluationFailedReason","evaluateTriggerFilter","filter","event","payload","trigger","Error","message","String"],"mappings":"AAAA,SACEA,wBAAwB,EACxBC,mCAAmC,EACnCC,8BAA8B,EAC9BC,+BAA+B,QAE1B,sBAAsB;AAG7B,wFAAwF;AACxF,qEAAqE;AAErE,OAAO,SAASC,iBACdC,YAAiC;IAEjC,MAAMC,QAAQD,aAAaE,MAAM,CAACC,IAAI;IACtC,IAAIF,UAAU,QAAQA,UAAUG,WAAW,OAAOA;IAClD,IAAI,OAAOH,UAAU,YAAYI,MAAMC,OAAO,CAACL,QAAQ,OAAOG;IAC9D,OAAOH;AACT;AAgBA,OAAO,SAASM,qBAAqBC,MAAkC;IACrE,MAAM,EAACP,KAAK,EAAC,GAAGO;IAChB,IAAIP,UAAU,QAAQA,UAAUG,WAAW,OAAO;QAACK,MAAM;IAAS;IAClE,IAAI,OAAOR,UAAU,YAAYA,MAAMS,IAAI,OAAO,IAAI;QACpD,OAAO;YACLD,MAAM;YACNE,QAAQH,OAAOI,aAAa;QAC9B;IACF;IAEA,IAAIC;IACJ,IAAI;QACFA,aAAalB,yBAAyB;YAACmB,QAAQb;YAAOc,OAAO;gBAACC,MAAM;YAAQ;QAAC;IAC/E,EAAE,OAAOC,OAAO;QACd,OAAO;YAACR,MAAM;YAAgBE,QAAQO,WAAWD;QAAM;IACzD;IAEA,MAAME,SAASvB,oCAAoCiB,YAAYL,OAAOY,OAAO;IAE7E,IAAID,OAAOE,gBAAgB,EAAE;QAC3B,OAAO;YAACZ,MAAM;YAAgBE,QAAQH,OAAOc,sBAAsB;QAAA;IACrE;IAEA,OAAOH,OAAOlB,KAAK,GAAG;QAACQ,MAAM;IAAS,IAAI;QAACA,MAAM;IAAU;AAC7D;AASA,OAAO,SAASc,sBACdf,MAAmC;IAEnC,OAAOD,qBAAqB;QAC1BN,OAAOO,OAAOR,YAAY,CAACE,MAAM,CAACsB,MAAM;QACxCJ,SAAStB,gCAAgC,kBAAkB;YACzD2B,OAAOjB,OAAOkB,OAAO;YACrBC,SAAS;gBAACb,QAAQN,OAAOM,MAAM;gBAAEW,OAAOjB,OAAOiB,KAAK;YAAA;QACtD;QACAb,eAAe;QACfU,wBAAwB;IAC1B;AACF;AAEA,SAASJ,WAAWD,KAAc;IAChC,IAAIA,iBAAiBpB,gCAAgC,OAAOoB,MAAMN,MAAM;IACxE,IAAIM,iBAAiBW,OAAO,OAAOX,MAAMY,OAAO;IAChD,OAAOC,OAAOb;AAChB"}
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch-integration-event.d.ts","sourceRoot":"","sources":["../../src/core/dispatch-integration-event.ts"],"names":[],"mappings":"AASA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAeD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,IAAI,CAAC,CAiHf"}
1
+ {"version":3,"file":"dispatch-integration-event.d.ts","sourceRoot":"","sources":["../../src/core/dispatch-integration-event.ts"],"names":[],"mappings":"AASA,OAAO,EAA2B,KAAK,qBAAqB,EAAC,MAAM,uBAAuB,CAAC;AAE3F,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAeD,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,IAAI,CAAC,CAmHf"}
@@ -63,6 +63,7 @@ export async function dispatchIntegrationEvent(params) {
63
63
  workspaceId: subscription.workspaceId,
64
64
  projectId: subscription.projectId,
65
65
  definitionId: subscription.workflowDefinitionId,
66
+ triggerConnectionId: params.connectionId,
66
67
  triggerPayload: {
67
68
  provider: params.provider,
68
69
  source: params.source,
@@ -95,6 +96,7 @@ export async function dispatchIntegrationEvent(params) {
95
96
  history,
96
97
  eventRef: params.eventRef,
97
98
  workspaceId: params.workspaceId,
99
+ connectionId: params.connectionId,
98
100
  provider: params.provider,
99
101
  source: params.source,
100
102
  event: params.event,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/dispatch-integration-event.ts"],"sourcesContent":["import {findMatchingSubscriptions} from '#db/subscriptions.js';\nimport {\n eventOutcomeCount,\n eventReceivedCount,\n subscriptionTriggeredCount,\n} from '#metrics/instance.js';\nimport {evaluateTriggerFilter, readConfigInputs} from './config.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {routeEventToJobListeners} from './route-event-to-job-listeners.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface DispatchIntegrationEventParams {\n workflows: WorkflowsModuleClient;\n eventRef: string;\n workspaceId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n connectionId: string;\n connectionName: string | null;\n payload: unknown;\n receivedAt: Date;\n}\n\n// Source-agnostic dispatcher: any inbound integration event fans out to every\n// workspace subscription registered for its (source, event) and to every listening\n// job subscribed to it, passing the raw payload through untouched. The module knows\n// nothing about github, gitlab, etc.\n//\n// Continue-on-error: every matched subscription and listener is attempted so one broken\n// subscription cannot starve its siblings. A permanent admission failure (deleted definition,\n// project mismatch, suspended workspace) is recorded and skipped; a transient one is recorded\n// and re-thrown so the outbox\n// replays the whole event and converges (succeeded siblings dedup on the idempotency key). The\n// event reaches a terminal outcome only when no transient error remains: `routed` if any run\n// was created or any listening job matched, `discarded` if nothing matched, otherwise `errored`.\n// History is best-effort; the thrown transient error, not the recorded outcome, drives the retry.\nexport async function dispatchIntegrationEvent(\n params: DispatchIntegrationEventParams,\n): Promise<void> {\n eventReceivedCount.add(1, {provider: params.provider});\n\n const history = await beginTriggerHistory({\n eventRef: params.eventRef,\n origin: 'integration',\n workspaceId: params.workspaceId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n connectionId: params.connectionId,\n connectionName: params.connectionName,\n payload: (params.payload ?? null) as Record<string, unknown> | null,\n receivedAt: params.receivedAt,\n });\n\n const subscriptions = await findMatchingSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let triggeredCount = 0;\n let triggerEngagedCount = 0;\n let sawTransientError = false;\n let firstTransientError: unknown;\n\n for (const subscription of subscriptions) {\n const filterResult = evaluateTriggerFilter({\n subscription,\n source: params.source,\n event: params.event,\n payload: params.payload,\n });\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n triggerEngagedCount += 1;\n await history.filterErrored(subscription, filterResult.reason);\n continue;\n }\n triggerEngagedCount += 1;\n\n const inputs = readConfigInputs(subscription);\n try {\n const run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerPayload: {\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n data: params.payload,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: `${subscription.id}:${params.eventRef}`,\n });\n await history.triggered(subscription, run);\n triggeredCount += 1;\n subscriptionTriggeredCount.add(1, {provider: params.provider});\n } catch (error) {\n await history.dispatchErrored(subscription, toReason(error));\n // Track presence with a flag, not `firstTransientError === undefined`: a thrown\n // value of `undefined` is still a transient failure and must drive the replay.\n if (!isPermanentStartRunError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n const listenerResult = await routeEventToJobListeners({\n workflows: params.workflows,\n history,\n eventRef: params.eventRef,\n workspaceId: params.workspaceId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n payload: params.payload,\n receivedAt: params.receivedAt,\n });\n\n if (listenerResult.transientErrored && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = listenerResult.transientError;\n }\n\n const totalMatchedCount = triggerEngagedCount + listenerResult.engagedCount;\n\n if (sawTransientError) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'failed'});\n await history.failed(totalMatchedCount);\n throw firstTransientError;\n }\n\n if (triggeredCount > 0 || listenerResult.acceptedJobCount > 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'routed'});\n await history.routed(totalMatchedCount);\n return;\n }\n\n if (totalMatchedCount === 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'discarded'});\n await history.discarded();\n return;\n }\n\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'errored'});\n await history.allErrored(totalMatchedCount);\n}\n"],"names":["findMatchingSubscriptions","eventOutcomeCount","eventReceivedCount","subscriptionTriggeredCount","evaluateTriggerFilter","readConfigInputs","beginTriggerHistory","toReason","routeEventToJobListeners","isPermanentStartRunError","dispatchIntegrationEvent","params","add","provider","history","eventRef","origin","workspaceId","source","event","deliveryId","connectionId","connectionName","payload","receivedAt","subscriptions","triggeredCount","triggerEngagedCount","sawTransientError","firstTransientError","subscription","filterResult","kind","filterErrored","reason","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerPayload","data","undefined","idempotencyKey","id","triggered","error","dispatchErrored","listenerResult","transientErrored","transientError","totalMatchedCount","engagedCount","outcome","failed","acceptedJobCount","routed","discarded","allErrored"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/dispatch-integration-event.ts"],"sourcesContent":["import {findMatchingSubscriptions} from '#db/subscriptions.js';\nimport {\n eventOutcomeCount,\n eventReceivedCount,\n subscriptionTriggeredCount,\n} from '#metrics/instance.js';\nimport {evaluateTriggerFilter, readConfigInputs} from './config.js';\nimport {beginTriggerHistory, toReason} from './record-trigger-history.js';\nimport {routeEventToJobListeners} from './route-event-to-job-listeners.js';\nimport {isPermanentStartRunError, type WorkflowsModuleClient} from './workflows-client.js';\n\nexport interface DispatchIntegrationEventParams {\n workflows: WorkflowsModuleClient;\n eventRef: string;\n workspaceId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n connectionId: string;\n connectionName: string | null;\n payload: unknown;\n receivedAt: Date;\n}\n\n// Source-agnostic dispatcher: any inbound integration event fans out to every\n// workspace subscription registered for its (source, event) and to every listening\n// job subscribed to it, passing the raw payload through untouched. The module knows\n// nothing about github, gitlab, etc.\n//\n// Continue-on-error: every matched subscription and listener is attempted so one broken\n// subscription cannot starve its siblings. A permanent admission failure (deleted definition,\n// project mismatch, suspended workspace) is recorded and skipped; a transient one is recorded\n// and re-thrown so the outbox\n// replays the whole event and converges (succeeded siblings dedup on the idempotency key). The\n// event reaches a terminal outcome only when no transient error remains: `routed` if any run\n// was created or any listening job matched, `discarded` if nothing matched, otherwise `errored`.\n// History is best-effort; the thrown transient error, not the recorded outcome, drives the retry.\nexport async function dispatchIntegrationEvent(\n params: DispatchIntegrationEventParams,\n): Promise<void> {\n eventReceivedCount.add(1, {provider: params.provider});\n\n const history = await beginTriggerHistory({\n eventRef: params.eventRef,\n origin: 'integration',\n workspaceId: params.workspaceId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n connectionId: params.connectionId,\n connectionName: params.connectionName,\n payload: (params.payload ?? null) as Record<string, unknown> | null,\n receivedAt: params.receivedAt,\n });\n\n const subscriptions = await findMatchingSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let triggeredCount = 0;\n let triggerEngagedCount = 0;\n let sawTransientError = false;\n let firstTransientError: unknown;\n\n for (const subscription of subscriptions) {\n const filterResult = evaluateTriggerFilter({\n subscription,\n source: params.source,\n event: params.event,\n payload: params.payload,\n });\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n triggerEngagedCount += 1;\n await history.filterErrored(subscription, filterResult.reason);\n continue;\n }\n triggerEngagedCount += 1;\n\n const inputs = readConfigInputs(subscription);\n try {\n const run = await params.workflows.startRunFromTrigger({\n workspaceId: subscription.workspaceId,\n projectId: subscription.projectId,\n definitionId: subscription.workflowDefinitionId,\n triggerConnectionId: params.connectionId,\n triggerPayload: {\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n data: params.payload,\n },\n ...(inputs === undefined ? {} : {inputs}),\n idempotencyKey: `${subscription.id}:${params.eventRef}`,\n });\n await history.triggered(subscription, run);\n triggeredCount += 1;\n subscriptionTriggeredCount.add(1, {provider: params.provider});\n } catch (error) {\n await history.dispatchErrored(subscription, toReason(error));\n // Track presence with a flag, not `firstTransientError === undefined`: a thrown\n // value of `undefined` is still a transient failure and must drive the replay.\n if (!isPermanentStartRunError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n const listenerResult = await routeEventToJobListeners({\n workflows: params.workflows,\n history,\n eventRef: params.eventRef,\n workspaceId: params.workspaceId,\n connectionId: params.connectionId,\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n payload: params.payload,\n receivedAt: params.receivedAt,\n });\n\n if (listenerResult.transientErrored && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = listenerResult.transientError;\n }\n\n const totalMatchedCount = triggerEngagedCount + listenerResult.engagedCount;\n\n if (sawTransientError) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'failed'});\n await history.failed(totalMatchedCount);\n throw firstTransientError;\n }\n\n if (triggeredCount > 0 || listenerResult.acceptedJobCount > 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'routed'});\n await history.routed(totalMatchedCount);\n return;\n }\n\n if (totalMatchedCount === 0) {\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'discarded'});\n await history.discarded();\n return;\n }\n\n eventOutcomeCount.add(1, {provider: params.provider, outcome: 'errored'});\n await history.allErrored(totalMatchedCount);\n}\n"],"names":["findMatchingSubscriptions","eventOutcomeCount","eventReceivedCount","subscriptionTriggeredCount","evaluateTriggerFilter","readConfigInputs","beginTriggerHistory","toReason","routeEventToJobListeners","isPermanentStartRunError","dispatchIntegrationEvent","params","add","provider","history","eventRef","origin","workspaceId","source","event","deliveryId","connectionId","connectionName","payload","receivedAt","subscriptions","triggeredCount","triggerEngagedCount","sawTransientError","firstTransientError","subscription","filterResult","kind","filterErrored","reason","inputs","run","workflows","startRunFromTrigger","projectId","definitionId","workflowDefinitionId","triggerConnectionId","triggerPayload","data","undefined","idempotencyKey","id","triggered","error","dispatchErrored","listenerResult","transientErrored","transientError","totalMatchedCount","engagedCount","outcome","failed","acceptedJobCount","routed","discarded","allErrored"],"mappings":"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"}
@@ -5,6 +5,7 @@ export interface RouteEventToJobListenersParams {
5
5
  history: TriggerHistoryRecorder;
6
6
  eventRef: string;
7
7
  workspaceId: string;
8
+ connectionId: string;
8
9
  provider: string;
9
10
  source: string;
10
11
  event: string;
@@ -1 +1 @@
1
- {"version":3,"file":"route-event-to-job-listeners.d.ts","sourceRoot":"","sources":["../../src/core/route-event-to-job-listeners.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,KAAK,sBAAsB,EAAW,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,8BAA8B,CAAC,CAmEzC"}
1
+ {"version":3,"file":"route-event-to-job-listeners.d.ts","sourceRoot":"","sources":["../../src/core/route-event-to-job-listeners.ts"],"names":[],"mappings":"AAKA,OAAO,EAAC,KAAK,sBAAsB,EAAW,MAAM,6BAA6B,CAAC;AAClF,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,qBAAqB,CAAC;IACjC,OAAO,EAAE,sBAAsB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,8BAA8B,CAAC,CAsFzC"}
@@ -1,3 +1,4 @@
1
+ import { projectWorkflowPredicateContext } from '@shipfox/expression';
1
2
  import { logger } from '@shipfox/node-opentelemetry';
2
3
  import { findMatchingJobListenerSubscriptions } from '#db/job-listener-subscriptions.js';
3
4
  import { evaluateStoredFilter } from './config.js';
@@ -27,6 +28,20 @@ export async function routeEventToJobListeners(params) {
27
28
  effectiveMatchByJobId.set(subscription.jobId, subscription);
28
29
  }
29
30
  }
31
+ const fireDeliveryNeeded = [
32
+ ...effectiveMatchByJobId.values()
33
+ ].some((subscription)=>listenerDisposition(subscription) === 'fire');
34
+ const triggerReference = fireDeliveryNeeded ? await params.workflows.resolveWorkflowRunTriggerReference({
35
+ workspaceId: params.workspaceId,
36
+ triggerConnectionId: params.connectionId,
37
+ triggerPayload: {
38
+ provider: params.provider,
39
+ source: params.source,
40
+ event: params.event,
41
+ deliveryId: params.deliveryId,
42
+ data: params.payload
43
+ }
44
+ }) : null;
30
45
  let acceptedJobCount = 0;
31
46
  let deliveredCount = 0;
32
47
  let sawTransientError = false;
@@ -43,6 +58,8 @@ export async function routeEventToJobListeners(params) {
43
58
  source: params.source,
44
59
  event: params.event,
45
60
  provider: params.provider,
61
+ triggerConnectionId: params.connectionId,
62
+ triggerReference,
46
63
  payload: params.payload,
47
64
  receivedAt: params.receivedAt.toISOString()
48
65
  });
@@ -77,9 +94,9 @@ function evaluateListenerFilter(params) {
77
94
  if (typeof filter !== 'string' || filter.trim() === '') {
78
95
  return evaluateStoredFilter({
79
96
  value: filter,
80
- context: {
97
+ context: projectWorkflowPredicateContext(params.subscription.kind === 'on' ? 'listener.on' : 'listener.until', {
81
98
  event: params.payload
82
- },
99
+ }),
83
100
  invalidReason: 'Listener subscription filter must be a non-empty string when set',
84
101
  evaluationFailedReason: 'Listener filter evaluation failed'
85
102
  });
@@ -88,10 +105,10 @@ function evaluateListenerFilter(params) {
88
105
  if (snapshot.kind === 'invalid') return snapshot.result;
89
106
  return evaluateStoredFilter({
90
107
  value: filter,
91
- context: {
108
+ context: projectWorkflowPredicateContext(params.subscription.kind === 'on' ? 'listener.on' : 'listener.until', {
92
109
  ...snapshot.value,
93
110
  event: params.payload
94
- },
111
+ }),
95
112
  invalidReason: 'Listener subscription filter must be a non-empty string when set',
96
113
  evaluationFailedReason: 'Listener filter evaluation failed'
97
114
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/route-event-to-job-listeners.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport {findMatchingJobListenerSubscriptions} from '#db/job-listener-subscriptions.js';\nimport {evaluateStoredFilter, type StoredFilterEvaluation} from './config.js';\nimport type {JobListenerSubscription} from './entities/job-listener-subscription.js';\nimport {type TriggerHistoryRecorder, toReason} from './record-trigger-history.js';\nimport {\n isPermanentDeliverEventToJobListenerError,\n type WorkflowsModuleClient,\n} from './workflows-client.js';\n\nexport interface RouteEventToJobListenersParams {\n workflows: WorkflowsModuleClient;\n history: TriggerHistoryRecorder;\n eventRef: string;\n workspaceId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n payload: unknown;\n receivedAt: Date;\n}\n\nexport interface RouteEventToJobListenersResult {\n /**\n * Listener outcomes that should contribute to received_event.matched_count:\n * filter errors, accepted deliveries, and dispatch errors. Stale skipped jobs\n * are intentionally excluded because they did not produce an auditable decision.\n */\n engagedCount: number;\n /**\n * Jobs with an effective matcher after source/event/filter checks, including\n * stale skipped jobs.\n */\n matchedJobCount: number;\n acceptedJobCount: number;\n deliveredCount: number;\n transientErrored: boolean;\n transientError: unknown;\n}\n\nexport async function routeEventToJobListeners(\n params: RouteEventToJobListenersParams,\n): Promise<RouteEventToJobListenersResult> {\n const subscriptions = await findMatchingJobListenerSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let filterErrorCount = 0;\n const effectiveMatchByJobId = new Map<string, JobListenerSubscription>();\n for (const subscription of subscriptions) {\n const filterResult = evaluateListenerFilter({subscription, payload: params.payload});\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n filterErrorCount += 1;\n await params.history.listenerFilterErrored(subscription, filterResult.reason);\n continue;\n }\n\n const previous = effectiveMatchByJobId.get(subscription.jobId);\n if (!previous || shouldReplaceEffectiveMatcher(previous, subscription)) {\n effectiveMatchByJobId.set(subscription.jobId, subscription);\n }\n }\n\n let acceptedJobCount = 0;\n let deliveredCount = 0;\n let sawTransientError = false;\n let dispatchErrorCount = 0;\n let firstTransientError: unknown;\n\n for (const subscription of effectiveMatchByJobId.values()) {\n const disposition = listenerDisposition(subscription);\n try {\n const result = await params.workflows.deliverEventToJobListener({\n jobId: subscription.jobId,\n disposition,\n eventRef: params.eventRef,\n deliveryId: params.deliveryId,\n source: params.source,\n event: params.event,\n provider: params.provider,\n payload: params.payload,\n receivedAt: params.receivedAt.toISOString(),\n });\n if (!result.skipped) {\n acceptedJobCount += 1;\n await params.history.listenerTriggered(subscription);\n }\n if (result.buffered) deliveredCount += 1;\n } catch (error) {\n dispatchErrorCount += 1;\n await params.history.listenerDispatchErrored(subscription, toReason(error));\n if (!isPermanentDeliverEventToJobListenerError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n return {\n engagedCount: filterErrorCount + acceptedJobCount + dispatchErrorCount,\n matchedJobCount: effectiveMatchByJobId.size,\n acceptedJobCount,\n deliveredCount,\n transientErrored: sawTransientError,\n transientError: sawTransientError ? firstTransientError : undefined,\n };\n}\n\ninterface EvaluateListenerFilterParams {\n subscription: JobListenerSubscription;\n payload: unknown;\n}\n\nfunction evaluateListenerFilter(params: EvaluateListenerFilterParams): StoredFilterEvaluation {\n const filter = params.subscription.config.filter;\n if (filter === null || filter === undefined) return {kind: 'matched'};\n if (typeof filter !== 'string' || filter.trim() === '') {\n return evaluateStoredFilter({\n value: filter,\n context: {event: params.payload},\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n }\n\n const snapshot = readFilterSnapshot(params.subscription);\n if (snapshot.kind === 'invalid') return snapshot.result;\n\n return evaluateStoredFilter({\n value: filter,\n context: {...snapshot.value, event: params.payload},\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n}\n\nfunction readFilterSnapshot(\n subscription: JobListenerSubscription,\n):\n | {kind: 'valid'; value: Record<string, unknown>}\n | {kind: 'invalid'; result: Extract<StoredFilterEvaluation, {kind: 'filter-error'}>} {\n const value = subscription.config.filter_snapshot;\n if (value === undefined) return {kind: 'valid', value: {}};\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return {kind: 'valid', value: value as Record<string, unknown>};\n }\n return {\n kind: 'invalid',\n result: {\n kind: 'filter-error',\n reason: 'Listener filter snapshot must be an object when set',\n },\n };\n}\n\nfunction listenerDisposition(subscription: JobListenerSubscription): 'fire' | 'resolve' {\n return subscription.kind === 'until' ? 'resolve' : 'fire';\n}\n\nfunction listenerMatcherSortKey(subscription: JobListenerSubscription): string {\n const kindRank = subscription.kind === 'until' ? '0' : '1';\n return `${kindRank}:${subscription.matcherOrdinal.toString().padStart(10, '0')}`;\n}\n\nfunction shouldReplaceEffectiveMatcher(\n previous: JobListenerSubscription,\n candidate: JobListenerSubscription,\n): boolean {\n const previousKey = listenerMatcherSortKey(previous);\n const candidateKey = listenerMatcherSortKey(candidate);\n if (candidateKey !== previousKey) return candidateKey < previousKey;\n\n logger().warn(\n {\n jobId: candidate.jobId,\n keptSubscriptionId: previous.id,\n candidateSubscriptionId: candidate.id,\n matcherKind: candidate.kind,\n matcherOrdinal: candidate.matcherOrdinal,\n },\n 'duplicate job listener matcher key encountered; choosing deterministic subscription id order',\n );\n return candidate.id < previous.id;\n}\n"],"names":["logger","findMatchingJobListenerSubscriptions","evaluateStoredFilter","toReason","isPermanentDeliverEventToJobListenerError","routeEventToJobListeners","params","subscriptions","workspaceId","source","event","filterErrorCount","effectiveMatchByJobId","Map","subscription","filterResult","evaluateListenerFilter","payload","kind","history","listenerFilterErrored","reason","previous","get","jobId","shouldReplaceEffectiveMatcher","set","acceptedJobCount","deliveredCount","sawTransientError","dispatchErrorCount","firstTransientError","values","disposition","listenerDisposition","result","workflows","deliverEventToJobListener","eventRef","deliveryId","provider","receivedAt","toISOString","skipped","listenerTriggered","buffered","error","listenerDispatchErrored","engagedCount","matchedJobCount","size","transientErrored","transientError","undefined","filter","config","trim","value","context","invalidReason","evaluationFailedReason","snapshot","readFilterSnapshot","filter_snapshot","Array","isArray","listenerMatcherSortKey","kindRank","matcherOrdinal","toString","padStart","candidate","previousKey","candidateKey","warn","keptSubscriptionId","id","candidateSubscriptionId","matcherKind"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/core/route-event-to-job-listeners.ts"],"sourcesContent":["import {projectWorkflowPredicateContext} from '@shipfox/expression';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {findMatchingJobListenerSubscriptions} from '#db/job-listener-subscriptions.js';\nimport {evaluateStoredFilter, type StoredFilterEvaluation} from './config.js';\nimport type {JobListenerSubscription} from './entities/job-listener-subscription.js';\nimport {type TriggerHistoryRecorder, toReason} from './record-trigger-history.js';\nimport {\n isPermanentDeliverEventToJobListenerError,\n type WorkflowsModuleClient,\n} from './workflows-client.js';\n\nexport interface RouteEventToJobListenersParams {\n workflows: WorkflowsModuleClient;\n history: TriggerHistoryRecorder;\n eventRef: string;\n workspaceId: string;\n connectionId: string;\n provider: string;\n source: string;\n event: string;\n deliveryId: string;\n payload: unknown;\n receivedAt: Date;\n}\n\nexport interface RouteEventToJobListenersResult {\n /**\n * Listener outcomes that should contribute to received_event.matched_count:\n * filter errors, accepted deliveries, and dispatch errors. Stale skipped jobs\n * are intentionally excluded because they did not produce an auditable decision.\n */\n engagedCount: number;\n /**\n * Jobs with an effective matcher after source/event/filter checks, including\n * stale skipped jobs.\n */\n matchedJobCount: number;\n acceptedJobCount: number;\n deliveredCount: number;\n transientErrored: boolean;\n transientError: unknown;\n}\n\nexport async function routeEventToJobListeners(\n params: RouteEventToJobListenersParams,\n): Promise<RouteEventToJobListenersResult> {\n const subscriptions = await findMatchingJobListenerSubscriptions({\n workspaceId: params.workspaceId,\n source: params.source,\n event: params.event,\n });\n\n let filterErrorCount = 0;\n const effectiveMatchByJobId = new Map<string, JobListenerSubscription>();\n for (const subscription of subscriptions) {\n const filterResult = evaluateListenerFilter({subscription, payload: params.payload});\n if (filterResult.kind === 'filtered') continue;\n if (filterResult.kind === 'filter-error') {\n filterErrorCount += 1;\n await params.history.listenerFilterErrored(subscription, filterResult.reason);\n continue;\n }\n\n const previous = effectiveMatchByJobId.get(subscription.jobId);\n if (!previous || shouldReplaceEffectiveMatcher(previous, subscription)) {\n effectiveMatchByJobId.set(subscription.jobId, subscription);\n }\n }\n\n const fireDeliveryNeeded = [...effectiveMatchByJobId.values()].some(\n (subscription) => listenerDisposition(subscription) === 'fire',\n );\n const triggerReference = fireDeliveryNeeded\n ? await params.workflows.resolveWorkflowRunTriggerReference({\n workspaceId: params.workspaceId,\n triggerConnectionId: params.connectionId,\n triggerPayload: {\n provider: params.provider,\n source: params.source,\n event: params.event,\n deliveryId: params.deliveryId,\n data: params.payload,\n },\n })\n : null;\n\n let acceptedJobCount = 0;\n let deliveredCount = 0;\n let sawTransientError = false;\n let dispatchErrorCount = 0;\n let firstTransientError: unknown;\n\n for (const subscription of effectiveMatchByJobId.values()) {\n const disposition = listenerDisposition(subscription);\n try {\n const result = await params.workflows.deliverEventToJobListener({\n jobId: subscription.jobId,\n disposition,\n eventRef: params.eventRef,\n deliveryId: params.deliveryId,\n source: params.source,\n event: params.event,\n provider: params.provider,\n triggerConnectionId: params.connectionId,\n triggerReference,\n payload: params.payload,\n receivedAt: params.receivedAt.toISOString(),\n });\n if (!result.skipped) {\n acceptedJobCount += 1;\n await params.history.listenerTriggered(subscription);\n }\n if (result.buffered) deliveredCount += 1;\n } catch (error) {\n dispatchErrorCount += 1;\n await params.history.listenerDispatchErrored(subscription, toReason(error));\n if (!isPermanentDeliverEventToJobListenerError(error) && !sawTransientError) {\n sawTransientError = true;\n firstTransientError = error;\n }\n }\n }\n\n return {\n engagedCount: filterErrorCount + acceptedJobCount + dispatchErrorCount,\n matchedJobCount: effectiveMatchByJobId.size,\n acceptedJobCount,\n deliveredCount,\n transientErrored: sawTransientError,\n transientError: sawTransientError ? firstTransientError : undefined,\n };\n}\n\ninterface EvaluateListenerFilterParams {\n subscription: JobListenerSubscription;\n payload: unknown;\n}\n\nfunction evaluateListenerFilter(params: EvaluateListenerFilterParams): StoredFilterEvaluation {\n const filter = params.subscription.config.filter;\n if (filter === null || filter === undefined) return {kind: 'matched'};\n if (typeof filter !== 'string' || filter.trim() === '') {\n return evaluateStoredFilter({\n value: filter,\n context: projectWorkflowPredicateContext(\n params.subscription.kind === 'on' ? 'listener.on' : 'listener.until',\n {event: params.payload},\n ),\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n }\n\n const snapshot = readFilterSnapshot(params.subscription);\n if (snapshot.kind === 'invalid') return snapshot.result;\n\n return evaluateStoredFilter({\n value: filter,\n context: projectWorkflowPredicateContext(\n params.subscription.kind === 'on' ? 'listener.on' : 'listener.until',\n {...snapshot.value, event: params.payload},\n ),\n invalidReason: 'Listener subscription filter must be a non-empty string when set',\n evaluationFailedReason: 'Listener filter evaluation failed',\n });\n}\n\nfunction readFilterSnapshot(\n subscription: JobListenerSubscription,\n):\n | {kind: 'valid'; value: Record<string, unknown>}\n | {kind: 'invalid'; result: Extract<StoredFilterEvaluation, {kind: 'filter-error'}>} {\n const value = subscription.config.filter_snapshot;\n if (value === undefined) return {kind: 'valid', value: {}};\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return {kind: 'valid', value: value as Record<string, unknown>};\n }\n return {\n kind: 'invalid',\n result: {\n kind: 'filter-error',\n reason: 'Listener filter snapshot must be an object when set',\n },\n };\n}\n\nfunction listenerDisposition(subscription: JobListenerSubscription): 'fire' | 'resolve' {\n return subscription.kind === 'until' ? 'resolve' : 'fire';\n}\n\nfunction listenerMatcherSortKey(subscription: JobListenerSubscription): string {\n const kindRank = subscription.kind === 'until' ? '0' : '1';\n return `${kindRank}:${subscription.matcherOrdinal.toString().padStart(10, '0')}`;\n}\n\nfunction shouldReplaceEffectiveMatcher(\n previous: JobListenerSubscription,\n candidate: JobListenerSubscription,\n): boolean {\n const previousKey = listenerMatcherSortKey(previous);\n const candidateKey = listenerMatcherSortKey(candidate);\n if (candidateKey !== previousKey) return candidateKey < previousKey;\n\n logger().warn(\n {\n jobId: candidate.jobId,\n keptSubscriptionId: previous.id,\n candidateSubscriptionId: candidate.id,\n matcherKind: candidate.kind,\n matcherOrdinal: candidate.matcherOrdinal,\n },\n 'duplicate job listener matcher key encountered; choosing deterministic subscription id order',\n );\n return candidate.id < previous.id;\n}\n"],"names":["projectWorkflowPredicateContext","logger","findMatchingJobListenerSubscriptions","evaluateStoredFilter","toReason","isPermanentDeliverEventToJobListenerError","routeEventToJobListeners","params","subscriptions","workspaceId","source","event","filterErrorCount","effectiveMatchByJobId","Map","subscription","filterResult","evaluateListenerFilter","payload","kind","history","listenerFilterErrored","reason","previous","get","jobId","shouldReplaceEffectiveMatcher","set","fireDeliveryNeeded","values","some","listenerDisposition","triggerReference","workflows","resolveWorkflowRunTriggerReference","triggerConnectionId","connectionId","triggerPayload","provider","deliveryId","data","acceptedJobCount","deliveredCount","sawTransientError","dispatchErrorCount","firstTransientError","disposition","result","deliverEventToJobListener","eventRef","receivedAt","toISOString","skipped","listenerTriggered","buffered","error","listenerDispatchErrored","engagedCount","matchedJobCount","size","transientErrored","transientError","undefined","filter","config","trim","value","context","invalidReason","evaluationFailedReason","snapshot","readFilterSnapshot","filter_snapshot","Array","isArray","listenerMatcherSortKey","kindRank","matcherOrdinal","toString","padStart","candidate","previousKey","candidateKey","warn","keptSubscriptionId","id","candidateSubscriptionId","matcherKind"],"mappings":"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"}
@@ -26,6 +26,7 @@ declare function startRunKnownError(error: unknown): import("@shipfox/inter-modu
26
26
  workspaceId: import("zod").ZodString;
27
27
  projectId: import("zod").ZodString;
28
28
  definitionId: import("zod").ZodString;
29
+ triggerConnectionId: import("zod").ZodOptional<import("zod").ZodString>;
29
30
  triggerPayload: import("zod").ZodUnion<readonly [import("zod").ZodObject<{
30
31
  provider: import("zod").ZodOptional<import("zod").ZodLiteral<"manual">>;
31
32
  source: import("zod").ZodLiteral<"manual">;
@@ -75,11 +76,19 @@ declare function startRunKnownError(error: unknown): import("@shipfox/inter-modu
75
76
  "agent.prompt": "agent.prompt";
76
77
  "agent.model": "agent.model";
77
78
  "agent.provider": "agent.provider";
79
+ "agent.thinking": "agent.thinking";
78
80
  "job.runner": "job.runner";
79
81
  "job.outputs": "job.outputs";
80
- "job.name": "job.name";
82
+ "job.execution_name": "job.execution_name";
83
+ "workflow.run_name": "workflow.run_name";
81
84
  "step.name": "step.name";
85
+ "step.working_directory": "step.working_directory";
82
86
  "step.feedback": "step.feedback";
87
+ "checkout.project": "checkout.project";
88
+ "checkout.connection": "checkout.connection";
89
+ "checkout.repository": "checkout.repository";
90
+ "checkout.ref": "checkout.ref";
91
+ "checkout.path": "checkout.path";
83
92
  }>;
84
93
  source: import("zod").ZodString;
85
94
  envKey: import("zod").ZodOptional<import("zod").ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"workflows-client.d.ts","sourceRoot":"","sources":["../../src/core/workflows-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,yCAAyC,CAAC;AAGjD,YAAY,EAAC,qBAAqB,EAAC,CAAC;AAEpC,KAAK,kBAAkB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAEpF;AAED,oFAAoF;AACpF,wBAAgB,yCAAyC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKjF;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAEnE;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAAE;IAAC,IAAI,EAAE,4BAA4B,CAAA;CAAC,CAAC,CAK/F;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAKzC"}
1
+ {"version":3,"file":"workflows-client.d.ts","sourceRoot":"","sources":["../../src/core/workflows-client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,yCAAyC,CAAC;AAGjD,YAAY,EAAC,qBAAqB,EAAC,CAAC;AAEpC,KAAK,kBAAkB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAEpF;AAED,oFAAoF;AACpF,wBAAgB,yCAAyC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKjF;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAC,CAAC,CAErE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,kBAAkB,EAAE;IAAC,IAAI,EAAE,mBAAmB,CAAA;CAAC,CAAC,CAEnE;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,EAAE;IAAC,IAAI,EAAE,4BAA4B,CAAA;CAAC,CAAC,CAK/F;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAKzC"}