@fluentcommerce/fluent-mcp-extn 0.2.0 → 0.3.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,1156 +1,1923 @@
1
- # Tool Reference
2
-
3
- This document describes every MCP tool exposed by `fluent-mcp-extn`, including
4
- purpose, required inputs, and example requests.
5
-
6
- ## Response envelope conventions
7
-
8
- ### Success
9
-
10
- ```json
11
- {
12
- "ok": true
13
- }
14
- ```
15
-
16
- Most tools include extra fields like `event`, `response`, `job`, or `status`.
17
-
18
- ### Failure
19
-
20
- ```json
21
- {
22
- "ok": false,
23
- "error": {
24
- "code": "VALIDATION_ERROR",
25
- "message": "Invalid tool arguments.",
26
- "retryable": false,
27
- "details": {}
28
- }
29
- }
30
- ```
31
-
32
- `error.retryable` indicates whether a caller can safely retry the same request. Non-idempotent writes intentionally avoid automatic retry behavior.
33
-
34
- ## `config.validate`
35
-
36
- Validates configuration and auth readiness without calling Fluent APIs.
37
-
38
- - **Input**: no fields
39
- - **Use when**: startup checks, CI preflight, environment validation
40
-
41
- Example:
42
-
43
- ```json
44
- {}
45
- ```
46
-
47
- ## `health.ping`
48
-
49
- Returns server health, SDK availability, and safe config summary.
50
-
51
- - **Input**: no fields
52
- - **Use when**: quick readiness checks in clients/agents
53
-
54
- Example:
55
-
56
- ```json
57
- {}
58
- ```
59
-
60
- ## `event.build`
61
-
62
- Builds a Fluent event payload only (no API call).
63
-
64
- - **Required**: `name`, `entityRef`, `entityType`
65
- - **Optional**: `entityId`, `rootEntityId`, `rootEntityType`, `source`, `type`, `attributes`, etc.
66
- - **Note**: if available in your flow, provide both `entityRef` and `entityId`.
67
-
68
- Example:
69
-
70
- ```json
71
- {
72
- "name": "ORDER_CREATED",
73
- "entityRef": "ORD-1001",
74
- "entityId": "12345",
75
- "entityType": "ORDER",
76
- "attributes": {
77
- "channel": "web"
78
- }
79
- }
80
- ```
81
-
82
- ## `event.send`
83
-
84
- Builds and sends an event via SDK adapter.
85
-
86
- - **Required**: `name`, `entityRef`, `entityType`
87
- - **Optional**: all `event.build` fields plus:
88
- - `mode`: `async` or `sync`
89
- - `dryRun`: boolean
90
- - **Important**: `retailerId` must match the target entity's retailer for real sends.
91
-
92
- Example dry-run:
93
-
94
- ```json
95
- {
96
- "name": "ORDER_CREATED",
97
- "entityRef": "ORD-1001",
98
- "entityId": "12345",
99
- "entityType": "ORDER",
100
- "dryRun": true
101
- }
102
- ```
103
-
104
- Example send:
105
-
106
- ```json
107
- {
108
- "name": "ORDER_CREATED",
109
- "entityRef": "ORD-1001",
110
- "entityId": "12345",
111
- "entityType": "ORDER",
112
- "mode": "async",
113
- "dryRun": false
114
- }
115
- ```
116
-
117
- ## `event.get`
118
-
119
- Gets one event by ID via SDK-native `getEventById(eventId)`:
120
-
121
- - endpoint: `/api/v4.1/event/{id}`
122
- - **Required**: `eventId`
123
-
124
- Example:
125
-
126
- ```json
127
- {
128
- "eventId": "7815ee32-5985-485f-863a-2643e57b64a2"
129
- }
130
- ```
131
-
132
- Typical success shape (truncated):
133
-
134
- ```json
135
- {
136
- "ok": true,
137
- "event": {
138
- "id": "7815ee32-5985-485f-863a-2643e57b64a2",
139
- "name": "ORDER_CREATED",
140
- "entityRef": "ORD-1001",
141
- "entityType": "ORDER",
142
- "retailerId": "5"
143
- }
144
- }
145
- ```
146
-
147
- ## `event.list`
148
-
149
- Lists/filter events via SDK-native `getEvents(params)`:
150
-
151
- - endpoint: `/api/v4.1/event`
152
- - **Canonical filters**:
153
- - `eventId`, `name`, `category`, `retailerId`
154
- - `eventType`, `eventStatus`, `from`, `to`
155
- - `start`, `count`
156
- - `context.rootEntityType`, `context.rootEntityId`, `context.rootEntityRef`
157
- - `context.entityType`, `context.entityId`, `context.entityRef`, `context.sourceEvents`
158
- - **Alias filters** (mapped internally):
159
- - `id` -> `eventId`
160
- - `entityRef` -> `context.entityRef`
161
- - `entityType` -> `context.entityType`
162
- - `type` -> `eventType`
163
- - **`context.sourceEvents` handling**:
164
- - accepts array input in tool arguments
165
- - serialized for query-string delivery to Event API
166
-
167
- Example:
168
-
169
- ```json
170
- {
171
- "eventType": "ORCHESTRATION_AUDIT",
172
- "eventStatus": "FAILED",
173
- "context.rootEntityType": "ORDER",
174
- "context.rootEntityRef": "ORD-1001",
175
- "retailerId": "5",
176
- "from": "2026-02-01T00:00:00.000Z",
177
- "to": "2026-02-15T23:59:59.999Z",
178
- "count": 25,
179
- "start": 1
180
- }
181
- ```
182
-
183
- Typical success shape (truncated):
184
-
185
- ```json
186
- {
187
- "ok": true,
188
- "events": {
189
- "start": 1,
190
- "count": 25,
191
- "hasMore": true,
192
- "results": [
193
- { "id": "event-id-1", "name": "ORDER_CREATED" }
194
- ]
195
- }
196
- }
197
- ```
198
-
199
- If `event.send` does not return an `id`, use `event.list` with `name`,
200
- `entityRef`, and `retailerId` to resolve the latest event ID, then call
201
- `event.get`.
202
-
203
- ## `event.flowInspect`
204
-
205
- One-call runtime forensics for any root entity.
206
-
207
- - **Required**: `rootEntityRef`
208
- - **Optional**:
209
- - `rootEntityType`: optional entity type filter (for example `ORDER`, `FULFILMENT`, `LOCATION`, `WAVE`, `PRODUCT`)
210
- - `rootEntityId`: optional root entity ID (helps disambiguate reused refs)
211
- - `compact`: return pre-analyzed summary instead of raw arrays (default `true`). Compact mode returns ~2-3k tokens with an `analysis` section; set to `false` for full ~24k raw data.
212
- - `from`, `to`: optional time window
213
- - `maxPages`: page cap per event type (`1..50`, default `10`)
214
- - `includeEventDetails`: include compact orchestration event rows (default `true`, full mode only)
215
- - `includeAuditDetails`: include compact audit action samples (default `false`, full mode only)
216
- - `includeScheduled`: fetch SCHEDULED events separately (default `true`)
217
- - `inspectStatuses`: statuses to drill via `event.get` (defaults: `["NO_MATCH", "PENDING", "FAILED"]`)
218
- - `maxDrilldowns`: cap on individual event.get calls (`0..100`, default `50`)
219
- - `actionSampleLimit`: max rows for action-derived sections (`1..200`, default `100`)
220
- - **Default-on flags:**
221
- - `compact`: pre-analyzed summary response (default `true`)
222
- - `includeExceptions`: rule exceptions with class, message, ruleset context (default `true`)
223
- - `includeNoMatchDetails`: enhanced NO_MATCH diagnostics from ruleSet audit events with closeMatches (default `true`)
224
- - **Opt-in flags (default false):**
225
- - `includeRuleDetails`: per-rule execution trace with class name, props, and timing (durationMs)
226
- - `includeCustomLogs`: custom plugin log messages (LogCollection from CUSTOM category)
227
- - `includeSnapshots`: entity state snapshots at each processing point
228
- - `includeCrossEntity`: fetch child entity events (FULFILMENT_CHOICE, FULFILMENT) using rootEntityRef
229
-
230
- **Compact mode (`compact: true`, the default):**
231
-
232
- Returns a pre-analyzed summary with:
233
- - `analysis.statusFlow`: ordered unique statuses seen (e.g., `["CREATED", "BOOKED", "SHIPPED"]`)
234
- - `analysis.findings[]`: anomaly detection — NO_MATCH (CRITICAL), FAILED/webhook errors/exceptions (HIGH), PENDING/slow rulesets (MEDIUM)
235
- - `analysis.failedWebhookEndpoints`: URLs that returned >= 400
236
- - `analysis.slowestRulesets`: top 3 by duration
237
- - `analysis.timespan`: first/last timestamps with durationMs
238
- - `audit.webhookActions`: only failures (responseCode >= 400)
239
- - `audit.mutationActions`: top 5 by queryName (name + count only)
240
- - `audit.sendEventActions`: count + scheduledCount only
241
- - `diagnostics.inspectedEvents`: summary (id, name, status, closeMatchCount)
242
-
243
- **Full mode (`compact: false`):**
244
-
245
- Returns complete raw arrays:
246
- - Orchestration timeline counts (`status`, `entityType`, top names)
247
- - Audit category/action breakdown with ruleset durations
248
- - Mutation payload evidence from ACTION audit (`DynamicUpdateMutation` request blocks)
249
- - Webhook diagnostics (`Request Endpoint`, `Response code`, `Response Body`, headers)
250
- - SendEvent payloads and future-dated scheduling evidence
251
- - Rule exceptions (when `includeExceptions: true`)
252
- - NO_MATCH closeMatches with mismatch reasons (when `includeNoMatchDetails: true`)
253
- - Per-rule execution with props/timing (when `includeRuleDetails: true`)
254
- - Custom plugin logs (when `includeCustomLogs: true`)
255
- - Entity snapshots (when `includeSnapshots: true`)
256
- - Cross-entity events with full events array (when `includeCrossEntity: true`)
257
- - `NO_MATCH`/`PENDING`/`FAILED` drilldown with full event payload via `event.get`
258
- - Auto-recommendations based on findings
259
-
260
- Example (compact — recommended first call):
261
-
262
- ```json
263
- {
264
- "rootEntityRef": "E2E_MULTI_202602221343",
265
- "rootEntityType": "ORDER"
266
- }
267
- ```
268
-
269
- Example (full data with all sections):
270
-
271
- ```json
272
- {
273
- "rootEntityRef": "ORD-001",
274
- "rootEntityType": "ORDER",
275
- "compact": false,
276
- "includeRuleDetails": true,
277
- "includeCustomLogs": true,
278
- "includeSnapshots": true,
279
- "includeCrossEntity": true
280
- }
281
- ```
282
-
283
- Example (lightweight):
284
-
285
- ```json
286
- {
287
- "rootEntityRef": "ORD-001",
288
- "rootEntityType": "ORDER",
289
- "includeEventDetails": false,
290
- "includeExceptions": false,
291
- "includeNoMatchDetails": false,
292
- "maxDrilldowns": 0
293
- }
294
- ```
295
-
296
- ## `metrics.query`
297
-
298
- Queries Prometheus metrics for Fluent runtime telemetry.
299
-
300
- - **Required**: `query` (PromQL expression)
301
- - **Optional**:
302
- - `type`: `instant` (default) or `range`
303
- - `time`: instant query timestamp
304
- - `start`, `end`, `step`: required together for range queries
305
- - `timeout`: query timeout in seconds (`1..120`)
306
- - **Execution path**:
307
- - instant -> GraphQL `metricInstant(query, time?)`
308
- - range -> GraphQL `metricRange(query, start, end, step)`
309
- - **Note**: `metrics.query` routes PromQL through Fluent's GraphQL proxy. Raw Prometheus REST endpoints are not relied on directly by this tool.
310
-
311
- Label hygiene reminder:
312
- - `core_event_received_total` does **not** include `status`
313
- - `rubix_event_runtime_seconds_*` does include `status`
314
- - histogram bucket series add `le`
315
-
316
- Accurate counter delta pattern for wider windows:
317
-
318
- ```promql
319
- (last_over_time(metric[window]) - metric offset <period>)
320
- or
321
- last_over_time(metric[window])
322
- ```
323
-
324
- Use the fallback `or` arm when offset samples are missing.
325
-
326
- Instant example:
327
-
328
- ```json
329
- {
330
- "query": "sum(rate(rubix_event_runtime_seconds_count[5m]))",
331
- "type": "instant"
332
- }
333
- ```
334
-
335
- Range example:
336
-
337
- ```json
338
- {
339
- "query": "rate(rubix_event_runtime_seconds_count{status=\"FAILED\"}[5m])",
340
- "type": "range",
341
- "start": "2026-02-20T00:00:00Z",
342
- "end": "2026-02-20T01:00:00Z",
343
- "step": "1m"
344
- }
345
- ```
346
-
347
- ## `metrics.topEvents`
348
-
349
- Returns ranked event analytics for a time window by aggregating Event API results.
350
-
351
- - **Required**: `from`
352
- - **Optional**:
353
- - `to`: end timestamp (defaults to now)
354
- - `entityType`: filter to one entity type
355
- - `eventType`: defaults to `ORCHESTRATION`
356
- - `topN`: ranked rows to return (`1..100`, default `20`)
357
- - `maxPages`: Event API pagination cap (`1..50`, default `10`)
358
- - **Output summary**:
359
- - `totalEvents`, `failureRate`, `statusBreakdown`
360
- - `topEvents[]` grouped by `name + entityType + status`
361
- - `uniqueEventNames`, `uniqueEntityTypes`
362
-
363
- Example:
364
-
365
- ```json
366
- {
367
- "from": "2026-02-22T00:00:00Z",
368
- "to": "2026-02-22T12:00:00Z",
369
- "topN": 20,
370
- "eventType": "ORCHESTRATION"
371
- }
372
- ```
373
-
374
- ## `metrics.healthCheck`
375
-
376
- Runs a compact, one-call anomaly assessment.
377
-
378
- - **Optional**:
379
- - `window`: Prometheus/Event API analysis window (`1h`, `6h`, `24h`, `7d`; default `1h`)
380
- - `includeTopEvents`: include ranked event list in response (default `true`)
381
- - `topN`: max top event rows (`1..100`, default `10`)
382
- - `thresholds`: override defaults
383
- - `failureRate` (default `5`)
384
- - `pendingRate` (default `10`)
385
- - `dominanceRate` (default `50`)
386
- - **Behavior**:
387
- - Prometheus-first (`metrics.query` equivalent calls under the hood)
388
- - Falls back to Event API aggregation if Prometheus is unavailable
389
-
390
- Example:
391
-
392
- ```json
393
- {
394
- "window": "1h",
395
- "includeTopEvents": true,
396
- "topN": 10
397
- }
398
- ```
399
-
400
- ## `metrics.sloReport`
401
-
402
- Returns a managed-services SLO snapshot with explicit KPI fields and threshold findings.
403
-
404
- - **Optional**:
405
- - `window`: KPI window (`30m`, `1h`, `24h`, `7d`; default `1h`)
406
- - `includeTopFailingEvents`: include top failed events from Event API (default `true`)
407
- - `topN`: top failed rows (`1..100`, default `10`)
408
- - `maxPages`: Event API pagination cap for fallback/failed ranking (`1..50`, default `10`)
409
- - `thresholds`: override defaults
410
- - `failureRate` (default `5`)
411
- - `noMatchRate` (default `0`)
412
- - `pendingRate` (default `10`)
413
- - `runtimeP95Seconds` (default `5`)
414
- - `inflightP95Seconds` (default `60`)
415
- - **Output highlights**:
416
- - total/failed/no-match/pending event counts
417
- - failure/no-match/pending rates
418
- - p95 runtime and inflight latency (null when Prometheus fallback path is used)
419
- - findings + recommendations + source (`prometheus` or `event_api`)
420
-
421
- Example:
422
-
423
- ```json
424
- {
425
- "window": "1h",
426
- "includeTopFailingEvents": true,
427
- "topN": 10
428
- }
429
- ```
430
-
431
- ## `metrics.labelCatalog`
432
-
433
- Discovers what labels a metric supports so PromQL groupings are correct.
434
-
435
- - **Required**: `metric`
436
- - **Optional**:
437
- - `window`: live series sampling window (`30m`, `1h`, `24h`, `7d`; default `24h`)
438
- - `includeKnownLabels`: include Fluent-known label hints (default `true`)
439
- - `maxValuesPerLabel`: max sample values returned per label (`1..50`, default `10`)
440
- - **Behavior**:
441
- - runs `last_over_time(<metric>[window])` as an instant query
442
- - extracts label keys from returned vector series
443
- - reports presence/cardinality/sample values per label
444
- - merges known Fluent label hints when available
445
-
446
- Example:
447
-
448
- ```json
449
- {
450
- "metric": "core_event_received_total",
451
- "window": "1h",
452
- "maxValuesPerLabel": 10
453
- }
454
- ```
455
-
456
- ## `workflow.transitions`
457
-
458
- Query available user actions (transitions) for entities at a given workflow state.
459
-
460
- Calls `POST /api/v4.1/transition` to discover what events can be fired, what attributes they require, and how they appear in the UI.
461
-
462
- - **Required**: `triggers` (array), each trigger requires `retailerId`
463
- - **Optional per trigger**: `type`, `subtype`, `status`, `module`, `flexType`, `flexVersion`, `name`
464
- - **Retailer behavior**: `retailerId` is required per trigger. Falls back to `FLUENT_RETAILER_ID` when omitted.
465
-
466
- ### Use cases
467
-
468
- - Discover available actions at any workflow status without reading workflow JSON
469
- - Build dynamic E2E test sequences that adapt to workflow changes
470
- - Validate that expected user actions are available after deployment
471
- - Get required event attributes for each action (avoids missing-attribute errors)
472
-
473
- ### Integration with event.send
474
-
475
- The `eventName` from each `userAction` maps directly to `event.send`'s `name` parameter. The `attributes[]` tell you what to include in `event.send`'s `attributes` parameter.
476
-
477
- ### Example: ORDER at CREATED status
478
-
479
- ```json
480
- {
481
- "triggers": [
482
- {
483
- "type": "ORDER",
484
- "subtype": "HD",
485
- "status": "CREATED",
486
- "retailerId": "5",
487
- "flexType": "ORDER::HD"
488
- }
489
- ]
490
- }
491
- ```
492
-
493
- ### Example: MANIFEST with module filter
494
-
495
- ```json
496
- {
497
- "triggers": [
498
- {
499
- "type": "MANIFEST",
500
- "subtype": "DEFAULT",
501
- "status": "PENDING",
502
- "module": "servicepoint",
503
- "flexType": "CARRIER::DEFAULT",
504
- "retailerId": "2"
505
- }
506
- ]
507
- }
508
- ```
509
-
510
- Typical success shape (truncated):
511
-
512
- ```json
513
- {
514
- "ok": true,
515
- "response": {
516
- "response": [
517
- {
518
- "trigger": {
519
- "type": "MANIFEST",
520
- "subtype": "DEFAULT",
521
- "status": "PENDING",
522
- "module": "servicepoint",
523
- "flexType": "CARRIER::DEFAULT",
524
- "retailerId": "2"
525
- },
526
- "userActions": [
527
- {
528
- "eventName": "UPDATE",
529
- "context": [
530
- { "label": "SUBMIT", "type": "PRIMARY", "modules": ["servicepoint"], "confirm": false }
531
- ],
532
- "attributes": []
533
- }
534
- ]
535
- }
536
- ]
537
- }
538
- }
539
- ```
540
-
541
- ### Dynamic test sequence pattern
542
-
543
- ```
544
- 1. Create entity via graphql.query (mutation)
545
- 2. workflow.transitions get available actions at current status
546
- 3. For each action:
547
- a. event.send with eventName and required attributes from response
548
- b. Poll entity status until transition completes
549
- c. workflow.transitions get next available actions
550
- 4. Repeat until no more userActions (terminal state)
551
- ```
552
-
553
- ## `plugin.list`
554
-
555
- List all registered orchestration rules (standard + custom) with metadata.
556
-
557
- Calls `GET /orchestration/rest/v1/plugin` and returns a map keyed by fully-qualified rule name (e.g. `ACCOUNT.context.RuleName`).
558
-
559
- - **Optional**: `name` (string) — case-insensitive substring filter on rule keys
560
- - **Requires**: SDK client with `request` method
561
-
562
- ### Each entry contains
563
-
564
- - `ruleInfo`: name, description, accepted entity types, produced events
565
- - `eventAttributes`: attributes the rule reads from events
566
- - `parameters`: configurable rule parameters (with types and descriptions)
567
-
568
- ### Use cases
569
-
570
- - Understand what rules do when analyzing workflows
571
- - Discover all registered rules (standard + custom) for a given account
572
- - Find rules by name pattern (e.g. all "SendEvent" variants)
573
- - Cross-reference deployed rules with local source code for module validation
574
-
575
- ### Example: list all rules
576
-
577
- ```json
578
- {}
579
- ```
580
-
581
- ### Example: filter by name
582
-
583
- ```json
584
- {
585
- "name": "SendEvent"
586
- }
587
- ```
588
-
589
- Typical success shape (truncated):
590
-
591
- ```json
592
- {
593
- "ok": true,
594
- "response": {
595
- "FLUENTRETAIL.base.SendEvent": {
596
- "ruleInfo": {
597
- "name": "SendEvent",
598
- "description": "Sends an event to a specified entity",
599
- "accepts": ["ORDER", "FULFILMENT", "ARTICLE"],
600
- "produces": ["SendEvent"]
601
- },
602
- "eventAttributes": [
603
- { "name": "eventName", "type": "STRING", "description": "Name of the event to send" }
604
- ],
605
- "parameters": [
606
- { "name": "eventName", "type": "STRING", "description": "Event name to dispatch" }
607
- ]
608
- }
609
- }
610
- }
611
- ```
612
-
613
- ## `graphql.query`
614
-
615
- Executes a Fluent Commerce GraphQL query or mutation through the SDK.
616
-
617
- - **Required**: `query`
618
- - **Optional**: `variables`
619
- - **Retry behavior**:
620
- - query operations use configured retry/backoff
621
- - mutation operations disable retries automatically to avoid duplicate writes
622
-
623
- ### Basic Query
624
-
625
- ```json
626
- {
627
- "query": "query { orders(first: 5) { edges { cursor node { id ref status } } pageInfo { hasNextPage } } }"
628
- }
629
- ```
630
-
631
- ### Pagination
632
-
633
- Fluent uses Relay-style connections. Cursors live on each **edge**, not on `pageInfo`. There is no `endCursor` or `startCursor`.
634
-
635
- ```json
636
- {
637
- "query": "query($cursor: String) { orders(first: 50, after: $cursor) { edges { cursor node { id ref status } } pageInfo { hasNextPage } } }",
638
- "variables": { "cursor": "Y3Vyc29yOi0tLTM2X18xNzcxMTc2MzMyMTc0" }
639
- }
640
- ```
641
-
642
- To paginate: take the `cursor` from the **last edge** in the response, pass it as the `after` variable. Repeat while `hasNextPage` is `true`.
643
-
644
- Pagination args: `first`/`after` (forward), `last`/`before` (backward).
645
-
646
- ### Mutation
647
-
648
- ```json
649
- {
650
- "query": "mutation($input: UpdateOrderInput!) { updateOrder(input: $input) { id ref status } }",
651
- "variables": { "input": { "id": "36", "status": "RECEIVED" } }
652
- }
653
- ```
654
-
655
- ### Synchronous Fulfilment Options (Live Checkout)
656
-
657
- `graphql.query` supports synchronous fulfilment options orchestration calls,
658
- commonly used by checkout journeys to get plans in the same response.
659
-
660
- ```json
661
- {
662
- "query": "mutation CreateFulfilmentOption($retailerId: Int!, $type: String!, $orderType: String!, $ref: String!, $products: [CreateFulfilmentOptionProductInput!], $longitude: Float!, $latitude: Float!, $radius: Json!) { createFulfilmentOption(input: { retailerId: $retailerId, type: $type, orderType: $orderType, ref: $ref, products: $products, address: { city: \"Kellyville Ridge\", state: \"New South Wales\", country: \"Australia\", postcode: \"2155\", addressLine1: \"Kellyville Ridge NSW, Australia\", latitude: $latitude, longitude: $longitude }, attributes: { name: \"radius\", type: \"DOUBLE\", value: $radius } }, executionMode: AWAIT_ORCHESTRATION) { id createdOn plans { edges { node { ref status eta splitCount fulfilments { locationRef eta items { productRef availableQuantity requestedQuantity } } } } } } }",
663
- "variables": {
664
- "retailerId": 5,
665
- "type": "CC",
666
- "orderType": "CC",
667
- "ref": "CHECKOUT-FO-12345",
668
- "products": [
669
- { "productRef": "MP09-34-Blue", "requestedQuantity": 1, "catalogueRef": "" }
670
- ],
671
- "longitude": 150.9193671,
672
- "latitude": -33.7068442,
673
- "radius": 50
674
- }
675
- }
676
- ```
677
-
678
- Notes:
679
- - `executionMode: AWAIT_ORCHESTRATION` makes the call synchronous.
680
- - The environment must contain a matching workflow for `type` (for example `FULFILMENT_OPTIONS::CC`).
681
- - If no matching workflow exists, Fluent returns a backend workflow-not-found error.
682
-
683
- ### Connection Shape
684
-
685
- All list queries return connections:
686
-
687
- ```graphql
688
- {
689
- edges {
690
- cursor
691
- node { ...fields }
692
- }
693
- pageInfo { hasNextPage }
694
- }
695
- ```
696
-
697
- Common query roots: `orders`, `fulfilments`, `fulfilmentChoices`, `locations`, `inventoryPositions`, `products`, `categories`, `settings`, `waves`, `articles`.
698
-
699
- ## `batch.create`
700
-
701
- Creates a Fluent ingestion job.
702
-
703
- - **Required**: `name`
704
- - **Optional**: `retailerId`, `entityType`, `action`
705
- - **Note**: `retailerId` falls back to `FLUENT_RETAILER_ID` if omitted
706
-
707
- Example:
708
-
709
- ```json
710
- {
711
- "name": "inventory-load-2026-02-06",
712
- "entityType": "INVENTORY_POSITION",
713
- "action": "UPSERT"
714
- }
715
- ```
716
-
717
- ## `batch.send`
718
-
719
- Sends payload records to an existing batch job.
720
-
721
- - **Required**:
722
- - `jobId`
723
- - `payload.action`
724
- - `payload.entityType`
725
- - `payload.entities` (non-empty array)
726
-
727
- Example:
728
-
729
- ```json
730
- {
731
- "jobId": "JOB-123",
732
- "payload": {
733
- "action": "UPSERT",
734
- "entityType": "INVENTORY_POSITION",
735
- "entities": [
736
- {
737
- "locationRef": "LOC-1",
738
- "skuRef": "SKU-1",
739
- "qty": 10,
740
- "type": "LAST_ON_HAND",
741
- "status": "ACTIVE"
742
- }
743
- ]
744
- }
745
- }
746
- ```
747
-
748
- ## `batch.status`
749
-
750
- Reads status for a previously created batch job.
751
-
752
- - **Required**: `jobId`
753
-
754
- Example:
755
-
756
- ```json
757
- {
758
- "jobId": "JOB-123"
759
- }
760
- ```
761
-
762
- ## `batch.batchStatus`
763
-
764
- Gets the status of a specific batch within a job. Useful for troubleshooting partial failures in multi-batch jobs.
765
-
766
- - **Required**: `jobId`, `batchId`
767
-
768
- Example:
769
-
770
- ```json
771
- {
772
- "jobId": "JOB-123",
773
- "batchId": "BATCH-456"
774
- }
775
- ```
776
-
777
- ## `batch.results`
778
-
779
- Gets per-record outcomes for a completed job. Call after `batch.status` reports a terminal state.
780
-
781
- - **Required**: `jobId`
782
-
783
- Example:
784
-
785
- ```json
786
- {
787
- "jobId": "JOB-123"
788
- }
789
- ```
790
-
791
- ---
792
-
793
- ## `graphql.queryAll`
794
-
795
- Executes a GraphQL query with SDK auto-pagination. Automatically follows cursors, merges all edges, and deduplicates by node ID. Use instead of `graphql.query` when you need ALL records from a connection.
796
-
797
- - **Required**: `query`
798
- - **Optional**:
799
- - `variables`: query variables (include cursor variable, usually `null` for first page)
800
- - `maxPages`: max pages to fetch (default: 100, max: 500)
801
- - `maxRecords`: max total records to accumulate (default: 10000, max: 50000)
802
- - `timeoutMs`: hard timeout for entire pagination run (default: 300000ms = 5 min)
803
- - `direction`: `forward` (first/after) or `backward` (last/before)
804
- - `errorHandling`: `throw` (fail on errors) or `partial` (return partial data + errors)
805
- - **Timeout behavior**:
806
- - uses `timeoutMs` for the whole pagination run
807
- - independent from `FLUENT_REQUEST_TIMEOUT_MS` single-call timeout
808
-
809
- ### How it works
810
-
811
- 1. SDK detects pagination variables (`first`/`after` or `last`/`before`) in your query
812
- 2. Executes the first page
813
- 3. Extracts cursor from the last edge, follows `hasNextPage`
814
- 4. Merges edges across pages, deduplicates by node ID
815
- 5. Stops when: no more pages, maxPages reached, maxRecords reached, or timeout
816
-
817
- ### Response
818
-
819
- Includes `extensions.autoPagination`:
820
-
821
- ```json
822
- {
823
- "totalPages": 5,
824
- "totalRecords": 487,
825
- "truncated": false,
826
- "direction": "forward"
827
- }
828
- ```
829
-
830
- If truncated: `truncationReason` will be `"maxPages"`, `"maxRecords"`, or `"timeout"`.
831
-
832
- ### Example: Fetch all active orders
833
-
834
- ```json
835
- {
836
- "query": "query($cursor: String) { orders(first: 100, after: $cursor, status: \"ACTIVE\") { edges { cursor node { id ref status createdOn } } pageInfo { hasNextPage } } }",
837
- "variables": { "cursor": null },
838
- "maxRecords": 5000
839
- }
840
- ```
841
-
842
- ### Example: Fetch all locations (backward)
843
-
844
- ```json
845
- {
846
- "query": "query($cursor: String) { locations(last: 50, before: $cursor) { edges { cursor node { id ref name type } } pageInfo { hasPreviousPage } } }",
847
- "variables": { "cursor": null },
848
- "direction": "backward"
849
- }
850
- ```
851
-
852
- ## `graphql.batchMutate`
853
-
854
- Executes multiple GraphQL mutations in a single request using aliased mutations. Sends up to 50 mutations at once, each with its own input.
855
-
856
- - **Required**: `mutation`, `inputs`
857
- - **Optional**: `returnFields`, `operationName`
858
- - **Retry behavior**: disabled (non-idempotent write operation)
859
-
860
- ### How it works
861
-
862
- 1. Builds an aliased mutation query:
863
- ```graphql
864
- mutation BatchUpdateOrders($input1: UpdateOrderInput!, $input2: UpdateOrderInput!) {
865
- updateOrder1: updateOrder(input: $input1) { id ref status }
866
- updateOrder2: updateOrder(input: $input2) { id ref status }
867
- }
868
- ```
869
- 2. Sends as a single GraphQL request
870
- 3. Parses per-mutation results (success/failure for each input)
871
-
872
- ### Response
873
-
874
- ```json
875
- {
876
- "ok": true,
877
- "summary": "All 3 mutations succeeded",
878
- "executed": 3,
879
- "failed": 0,
880
- "allSucceeded": true,
881
- "allFailed": false,
882
- "results": [
883
- { "alias": "updateOrder1", "index": 0, "success": true, "data": { "id": "1", "ref": "ORD-001", "status": "SHIPPED" } },
884
- { "alias": "updateOrder2", "index": 1, "success": true, "data": { "id": "2", "ref": "ORD-002", "status": "SHIPPED" } }
885
- ]
886
- }
887
- ```
888
-
889
- On partial failure, `errors` array includes per-mutation error details with `alias`, `index`, `message`, and `inputRef`.
890
-
891
- ### Example: Bulk update order statuses
892
-
893
- ```json
894
- {
895
- "mutation": "updateOrder",
896
- "inputs": [
897
- { "id": "36", "status": "SHIPPED" },
898
- { "id": "37", "status": "SHIPPED" },
899
- { "id": "38", "status": "SHIPPED" }
900
- ],
901
- "returnFields": ["id", "ref", "status"]
902
- }
903
- ```
904
-
905
- ### Example: Batch create inventory positions
906
-
907
- ```json
908
- {
909
- "mutation": "updateInventoryPosition",
910
- "inputs": [
911
- { "id": "100", "status": "ACTIVE" },
912
- { "id": "101", "status": "ACTIVE" }
913
- ],
914
- "returnFields": ["id", "ref", "status", "onHand"]
915
- }
916
- ```
917
-
918
- ## `graphql.introspect`
919
-
920
- Inspects the Fluent Commerce GraphQL schema via introspection. Fetches the full schema and caches it for 1 hour. Use to discover mutations, input types, and field requirements at runtime.
921
-
922
- - **Optional** (specify one mode):
923
- - `type`: name of an INPUT_OBJECT type to inspect (e.g., `UpdateOrderInput`)
924
- - `mutation`: name of a mutation to inspect (e.g., `updateOrder`)
925
- - `listMutations`: `true` to list all available mutation names
926
- - `listQueries`: `true` to list all available query root field names
927
-
928
- ### Example: List all mutations
929
-
930
- ```json
931
- {
932
- "listMutations": true
933
- }
934
- ```
935
-
936
- Response:
937
-
938
- ```json
939
- {
940
- "ok": true,
941
- "mutations": ["createOrder", "updateOrder", "createFulfilment", "..."],
942
- "count": 142
943
- }
944
- ```
945
-
946
- ### Example: Inspect a mutation
947
-
948
- ```json
949
- {
950
- "mutation": "updateOrder"
951
- }
952
- ```
953
-
954
- Response:
955
-
956
- ```json
957
- {
958
- "ok": true,
959
- "mutation": {
960
- "name": "updateOrder",
961
- "description": "Updates an existing order",
962
- "args": [
963
- {
964
- "name": "input",
965
- "type": "UpdateOrderInput!",
966
- "required": true
967
- }
968
- ],
969
- "returnType": "Order"
970
- }
971
- }
972
- ```
973
-
974
- ### Example: Inspect an input type
975
-
976
- ```json
977
- {
978
- "type": "UpdateOrderInput"
979
- }
980
- ```
981
-
982
- Response:
983
-
984
- ```json
985
- {
986
- "ok": true,
987
- "inputType": {
988
- "name": "UpdateOrderInput",
989
- "fields": [
990
- { "name": "id", "type": "ID!", "required": true },
991
- { "name": "ref", "type": "String", "required": false },
992
- { "name": "status", "type": "String", "required": false },
993
- { "name": "attributes", "type": "[AttributeInput]", "required": false, "isArray": true }
994
- ]
995
- }
996
- }
997
- ```
998
-
999
- ### Chaining example
1000
-
1001
- 1. `{ "listMutations": true }` → find `updateOrder`
1002
- 2. `{ "mutation": "updateOrder" }` → see it takes `UpdateOrderInput!`
1003
- 3. `{ "type": "UpdateOrderInput" }` → see all updatable fields
1004
- 4. Use `graphql.query` or `graphql.batchMutate` with the discovered schema
1005
-
1006
- ## `connection.test`
1007
-
1008
- Comprehensive Fluent Commerce connectivity test. Authenticates, executes a `me` query, and returns the authenticated user's details.
1009
-
1010
- - **Input**: no fields
1011
-
1012
- ### Response (success)
1013
-
1014
- ```json
1015
- {
1016
- "ok": true,
1017
- "duration": 342,
1018
- "details": {
1019
- "userId": "12",
1020
- "username": "admin@hmtest.com",
1021
- "userType": "RETAILER",
1022
- "userStatus": "ACTIVE",
1023
- "retailerId": "5",
1024
- "retailerRef": "HMTEST",
1025
- "retailerName": "HM Test",
1026
- "locationId": "10",
1027
- "locationRef": "W252",
1028
- "locationName": "Warehouse 252"
1029
- }
1030
- }
1031
- ```
1032
-
1033
- ### Response (failure)
1034
-
1035
- ```json
1036
- {
1037
- "ok": false,
1038
- "duration": 1500,
1039
- "error": "Authentication failed after 3 retries: 401 Unauthorized"
1040
- }
1041
- ```
1042
-
1043
- More thorough than `health.ping` — actually verifies the GraphQL endpoint works end-to-end. Use when first connecting, debugging auth issues, or verifying retailer/location context.
1044
-
1045
- ## `webhook.validate`
1046
-
1047
- Validates a Fluent Commerce webhook payload and optionally verifies its signature.
1048
-
1049
- - **Required**: `payload`
1050
- - **Optional**:
1051
- - `rawBody`: exact original HTTP request body string for signature checks
1052
- - `signature`: the `X-Fluent-Signature` header value (base64-encoded)
1053
- - `publicKey`: the Fluent public key (PEM format) for signature verification
1054
- - `algorithm`: `SHA512withRSA` (default) or `MD5withRSA`
1055
-
1056
- ### Mode 1: Basic validation
1057
-
1058
- Checks that required fields (`name`, `id`, `retailerId`) are present in the payload.
1059
-
1060
- ```json
1061
- {
1062
- "payload": {
1063
- "name": "OrderCreated",
1064
- "id": "event-123",
1065
- "retailerId": "5",
1066
- "entityType": "ORDER",
1067
- "entityRef": "ORD-001"
1068
- }
1069
- }
1070
- ```
1071
-
1072
- Response:
1073
-
1074
- ```json
1075
- {
1076
- "ok": true,
1077
- "basicValidation": { "valid": true, "missingFields": [] },
1078
- "note": "Basic field validation passed. Provide signature + publicKey for signature verification."
1079
- }
1080
- ```
1081
-
1082
- ### Mode 2: Signature validation
1083
-
1084
- Verifies the webhook body against the `X-Fluent-Signature` header using the provided public key.
1085
- Use `rawBody` whenever possible because signature verification is byte-sensitive.
1086
-
1087
- ```json
1088
- {
1089
- "payload": {
1090
- "name": "OrderCreated",
1091
- "id": "event-123",
1092
- "retailerId": "5"
1093
- },
1094
- "rawBody": "{\"name\":\"OrderCreated\",\"id\":\"event-123\",\"retailerId\":\"5\"}",
1095
- "signature": "base64-encoded-signature-from-header",
1096
- "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----",
1097
- "algorithm": "SHA512withRSA"
1098
- }
1099
- ```
1100
-
1101
- Response:
1102
-
1103
- ```json
1104
- {
1105
- "ok": true,
1106
- "basicValidation": { "valid": true, "missingFields": [] },
1107
- "signatureValidation": {
1108
- "valid": true,
1109
- "algorithm": "SHA512withRSA",
1110
- "payloadSource": "rawBody"
1111
- }
1112
- }
1113
- ```
1114
-
1115
- ---
1116
-
1117
- ## Retry and idempotency matrix
1118
-
1119
- | Category | Tools | Retry behavior |
1120
- |------|----------|-------------|
1121
- | Read operations | `event.get`, `event.list`, `event.flowInspect`, `metrics.query`, `metrics.healthCheck`, `metrics.sloReport`, `metrics.labelCatalog`, `metrics.topEvents`, `workflow.transitions`, `graphql.query` (query), `graphql.queryAll`, `batch.status`, `batch.batchStatus`, `batch.results`, `graphql.introspect`, `connection.test` | retry + backoff |
1122
- | Write operations | `event.send`, `batch.create`, `batch.send`, `graphql.query` (mutation), `graphql.batchMutate` | timeout-only (no automatic retry) |
1123
- | Local validation / no API | `config.validate`, `health.ping`, `event.build`, `webhook.validate` | not applicable |
1124
-
1125
- ## Tool inventory summary
1126
-
1127
- | Tool | Category | Requires SDK | Description |
1128
- |------|----------|:------------:|-------------|
1129
- | `config.validate` | Config | No | Validate auth/base URL configuration |
1130
- | `health.ping` | Config | No | Quick health check with config summary |
1131
- | `event.build` | Events | No | Build event payload (no API call) |
1132
- | `event.send` | Events | Yes | Send event (async/sync, supports dryRun) |
1133
- | `event.get` | Events | Yes | Get single event by ID |
1134
- | `event.list` | Events | Yes | List/filter events with pagination |
1135
- | `event.flowInspect` | Events | Yes | One-call root-entity flow forensics (mutations/webhooks/scheduled/exceptions/no-match/rules/snapshots/cross-entity) |
1136
- | `metrics.query` | Metrics | Yes | Query Prometheus metrics (instant/range) |
1137
- | `metrics.healthCheck` | Metrics | Yes | One-call anomaly assessment with fallback |
1138
- | `metrics.sloReport` | Metrics | Yes | SLO snapshot with rates, latency, and threshold findings |
1139
- | `metrics.labelCatalog` | Metrics | Yes | Label discovery for a metric (live sampling + known hints) |
1140
- | `metrics.topEvents` | Metrics | Yes | Ranked event analytics using Event API aggregation |
1141
- | `workflow.transitions` | Workflow | Yes | Query available user actions/transitions at any entity state |
1142
- | `graphql.query` | GraphQL | Yes | Execute single-page query or mutation |
1143
- | `graphql.queryAll` | GraphQL | Yes | Auto-paginated query (all records) |
1144
- | `graphql.batchMutate` | GraphQL | Yes | Multiple aliased mutations in one request |
1145
- | `graphql.introspect` | GraphQL | Yes | Schema introspection (types, mutations) |
1146
- | `batch.create` | Batch | Yes | Create ingestion job |
1147
- | `batch.send` | Batch | Yes | Send records to job |
1148
- | `batch.status` | Batch | Yes | Check job status |
1149
- | `batch.batchStatus` | Batch | Yes | Check specific batch status |
1150
- | `batch.results` | Batch | Yes | Get per-record job outcomes |
1151
- | `plugin.list` | Orchestration | Yes | List registered rules with optional name filter |
1152
- | `connection.test` | Diagnostics | Yes | Full connectivity test (user/retailer/location) |
1153
- | `webhook.validate` | Webhooks | No* | Validate webhook payload + signature |
1154
-
1155
- \* `webhook.validate` does not call Fluent APIs. Signature validation uses local crypto.
1156
-
1
+ # Tool Reference
2
+
3
+ This document describes every MCP tool exposed by `fluent-mcp-extn`, including
4
+ purpose, required inputs, and example requests.
5
+
6
+ ## Response envelope conventions
7
+
8
+ ### Success
9
+
10
+ ```json
11
+ {
12
+ "ok": true
13
+ }
14
+ ```
15
+
16
+ Most tools include extra fields like `event`, `response`, `job`, or `status`.
17
+
18
+ ### Failure
19
+
20
+ ```json
21
+ {
22
+ "ok": false,
23
+ "error": {
24
+ "code": "VALIDATION_ERROR",
25
+ "message": "Invalid tool arguments.",
26
+ "retryable": false,
27
+ "details": {}
28
+ }
29
+ }
30
+ ```
31
+
32
+ `error.retryable` indicates whether a caller can safely retry the same request. Non-idempotent writes intentionally avoid automatic retry behavior.
33
+
34
+ ## `config.validate`
35
+
36
+ Validates configuration and auth readiness without calling Fluent APIs.
37
+
38
+ - **Input**: no fields
39
+ - **Use when**: startup checks, CI preflight, environment validation
40
+
41
+ Example:
42
+
43
+ ```json
44
+ {}
45
+ ```
46
+
47
+ ## `health.ping`
48
+
49
+ Returns server health, SDK availability, and safe config summary.
50
+
51
+ - **Input**: no fields
52
+ - **Use when**: quick readiness checks in clients/agents
53
+
54
+ Example:
55
+
56
+ ```json
57
+ {}
58
+ ```
59
+
60
+ ## `event.build`
61
+
62
+ Builds a Fluent event payload only (no API call).
63
+
64
+ - **Required**: `name`, `entityRef`, `entityType`
65
+ - **Optional**: `entityId`, `rootEntityId`, `rootEntityType`, `source`, `type`, `attributes`, etc.
66
+ - **Note**: if available in your flow, provide both `entityRef` and `entityId`.
67
+
68
+ Example:
69
+
70
+ ```json
71
+ {
72
+ "name": "ORDER_CREATED",
73
+ "entityRef": "ORD-1001",
74
+ "entityId": "12345",
75
+ "entityType": "ORDER",
76
+ "attributes": {
77
+ "channel": "web"
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## `event.send`
83
+
84
+ Builds and sends an event via SDK adapter.
85
+
86
+ - **Required**: `name`, `entityRef`, `entityType`
87
+ - **Optional**: all `event.build` fields plus:
88
+ - `mode`: `async` or `sync`
89
+ - `dryRun`: boolean
90
+ - **Important**: `retailerId` must match the target entity's retailer for real sends.
91
+
92
+ Example dry-run:
93
+
94
+ ```json
95
+ {
96
+ "name": "ORDER_CREATED",
97
+ "entityRef": "ORD-1001",
98
+ "entityId": "12345",
99
+ "entityType": "ORDER",
100
+ "dryRun": true
101
+ }
102
+ ```
103
+
104
+ Example send:
105
+
106
+ ```json
107
+ {
108
+ "name": "ORDER_CREATED",
109
+ "entityRef": "ORD-1001",
110
+ "entityId": "12345",
111
+ "entityType": "ORDER",
112
+ "mode": "async",
113
+ "dryRun": false
114
+ }
115
+ ```
116
+
117
+ ## `event.get`
118
+
119
+ Gets one event by ID via SDK-native `getEventById(eventId)`:
120
+
121
+ - endpoint: `/api/v4.1/event/{id}`
122
+ - **Required**: `eventId`
123
+
124
+ Example:
125
+
126
+ ```json
127
+ {
128
+ "eventId": "7815ee32-5985-485f-863a-2643e57b64a2"
129
+ }
130
+ ```
131
+
132
+ Typical success shape (truncated):
133
+
134
+ ```json
135
+ {
136
+ "ok": true,
137
+ "event": {
138
+ "id": "7815ee32-5985-485f-863a-2643e57b64a2",
139
+ "name": "ORDER_CREATED",
140
+ "entityRef": "ORD-1001",
141
+ "entityType": "ORDER",
142
+ "retailerId": "5"
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## `event.list`
148
+
149
+ Lists/filter events via SDK-native `getEvents(params)`:
150
+
151
+ - endpoint: `/api/v4.1/event`
152
+ - **Canonical filters**:
153
+ - `eventId`, `name`, `category`, `retailerId`
154
+ - `eventType`, `eventStatus`, `from`, `to`
155
+ - `start`, `count`
156
+ - `context.rootEntityType`, `context.rootEntityId`, `context.rootEntityRef`
157
+ - `context.entityType`, `context.entityId`, `context.entityRef`, `context.sourceEvents`
158
+ - **Alias filters** (mapped internally):
159
+ - `id` -> `eventId`
160
+ - `entityRef` -> `context.entityRef`
161
+ - `entityType` -> `context.entityType`
162
+ - `type` -> `eventType`
163
+ - **`context.sourceEvents` handling**:
164
+ - accepts array input in tool arguments
165
+ - serialized for query-string delivery to Event API
166
+
167
+ Example:
168
+
169
+ ```json
170
+ {
171
+ "eventType": "ORCHESTRATION_AUDIT",
172
+ "eventStatus": "FAILED",
173
+ "context.rootEntityType": "ORDER",
174
+ "context.rootEntityRef": "ORD-1001",
175
+ "retailerId": "5",
176
+ "from": "2026-02-01T00:00:00.000Z",
177
+ "to": "2026-02-15T23:59:59.999Z",
178
+ "count": 25,
179
+ "start": 1
180
+ }
181
+ ```
182
+
183
+ Typical success shape (truncated):
184
+
185
+ ```json
186
+ {
187
+ "ok": true,
188
+ "events": {
189
+ "start": 1,
190
+ "count": 25,
191
+ "hasMore": true,
192
+ "results": [
193
+ { "id": "event-id-1", "name": "ORDER_CREATED" }
194
+ ]
195
+ }
196
+ }
197
+ ```
198
+
199
+ If `event.send` does not return an `id`, use `event.list` with `name`,
200
+ `entityRef`, and `retailerId` to resolve the latest event ID, then call
201
+ `event.get`.
202
+
203
+ ## `event.flowInspect`
204
+
205
+ One-call runtime forensics for any root entity.
206
+
207
+ - **Required**: `rootEntityRef`
208
+ - **Optional**:
209
+ - `rootEntityType`: optional entity type filter (for example `ORDER`, `FULFILMENT`, `LOCATION`, `WAVE`, `PRODUCT`)
210
+ - `rootEntityId`: optional root entity ID (helps disambiguate reused refs)
211
+ - `compact`: return pre-analyzed summary instead of raw arrays (default `true`). Compact mode returns ~2-3k tokens with an `analysis` section; set to `false` for full ~24k raw data.
212
+ - `from`, `to`: optional time window
213
+ - `maxPages`: page cap per event type (`1..50`, default `10`)
214
+ - `includeEventDetails`: include compact orchestration event rows (default `true`, full mode only)
215
+ - `includeAuditDetails`: include compact audit action samples (default `false`, full mode only)
216
+ - `includeScheduled`: fetch SCHEDULED events separately (default `true`)
217
+ - `inspectStatuses`: statuses to drill via `event.get` (defaults: `["NO_MATCH", "PENDING", "FAILED"]`)
218
+ - `maxDrilldowns`: cap on individual event.get calls (`0..100`, default `50`)
219
+ - `actionSampleLimit`: max rows for action-derived sections (`1..200`, default `100`)
220
+ - **Default-on flags:**
221
+ - `compact`: pre-analyzed summary response (default `true`)
222
+ - `includeExceptions`: rule exceptions with class, message, ruleset context (default `true`)
223
+ - `includeNoMatchDetails`: enhanced NO_MATCH diagnostics from ruleSet audit events with closeMatches (default `true`)
224
+ - **Opt-in flags (default false):**
225
+ - `includeRuleDetails`: per-rule execution trace with class name, props, and timing (durationMs)
226
+ - `includeCustomLogs`: custom plugin log messages (LogCollection from CUSTOM category)
227
+ - `includeSnapshots`: entity state snapshots at each processing point
228
+ - `includeCrossEntity`: fetch child entity events (FULFILMENT_CHOICE, FULFILMENT) using rootEntityRef
229
+
230
+ **Compact mode (`compact: true`, the default):**
231
+
232
+ Returns a pre-analyzed summary with:
233
+ - `analysis.statusFlow`: ordered unique statuses seen (e.g., `["CREATED", "BOOKED", "SHIPPED"]`)
234
+ - `analysis.findings[]`: anomaly detection — NO_MATCH (CRITICAL), FAILED/webhook errors/exceptions (HIGH), PENDING/slow rulesets (MEDIUM)
235
+ - `analysis.failedWebhookEndpoints`: URLs that returned >= 400
236
+ - `analysis.slowestRulesets`: top 3 by duration
237
+ - `analysis.timespan`: first/last timestamps with durationMs
238
+ - `audit.webhookActions`: only failures (responseCode >= 400)
239
+ - `audit.mutationActions`: top 5 by queryName (name + count only)
240
+ - `audit.sendEventActions`: count + scheduledCount only
241
+ - `diagnostics.inspectedEvents`: summary (id, name, status, closeMatchCount)
242
+
243
+ **Full mode (`compact: false`):**
244
+
245
+ Returns complete raw arrays:
246
+ - Orchestration timeline counts (`status`, `entityType`, top names)
247
+ - Audit category/action breakdown with ruleset durations
248
+ - Mutation payload evidence from ACTION audit (`DynamicUpdateMutation` request blocks)
249
+ - Webhook diagnostics (`Request Endpoint`, `Response code`, `Response Body`, headers)
250
+ - SendEvent payloads and future-dated scheduling evidence
251
+ - Rule exceptions (when `includeExceptions: true`)
252
+ - NO_MATCH closeMatches with mismatch reasons (when `includeNoMatchDetails: true`)
253
+ - Per-rule execution with props/timing (when `includeRuleDetails: true`)
254
+ - Custom plugin logs (when `includeCustomLogs: true`)
255
+ - Entity snapshots (when `includeSnapshots: true`)
256
+ - Cross-entity events with full events array (when `includeCrossEntity: true`)
257
+ - `NO_MATCH`/`PENDING`/`FAILED` drilldown with full event payload via `event.get`
258
+ - Auto-recommendations based on findings
259
+
260
+ Example (compact — recommended first call):
261
+
262
+ ```json
263
+ {
264
+ "rootEntityRef": "E2E_MULTI_202602221343",
265
+ "rootEntityType": "ORDER"
266
+ }
267
+ ```
268
+
269
+ Example (full data with all sections):
270
+
271
+ ```json
272
+ {
273
+ "rootEntityRef": "ORD-001",
274
+ "rootEntityType": "ORDER",
275
+ "compact": false,
276
+ "includeRuleDetails": true,
277
+ "includeCustomLogs": true,
278
+ "includeSnapshots": true,
279
+ "includeCrossEntity": true
280
+ }
281
+ ```
282
+
283
+ Example (lightweight):
284
+
285
+ ```json
286
+ {
287
+ "rootEntityRef": "ORD-001",
288
+ "rootEntityType": "ORDER",
289
+ "includeEventDetails": false,
290
+ "includeExceptions": false,
291
+ "includeNoMatchDetails": false,
292
+ "maxDrilldowns": 0
293
+ }
294
+ ```
295
+
296
+ ## `metrics.query`
297
+
298
+ Queries Prometheus metrics for Fluent runtime telemetry.
299
+
300
+ - **Required**: `query` (PromQL expression)
301
+ - **Optional**:
302
+ - `type`: `instant` (default) or `range`
303
+ - `time`: instant query timestamp
304
+ - `start`, `end`, `step`: required together for range queries
305
+ - `timeout`: query timeout in seconds (`1..120`)
306
+ - **Execution path**:
307
+ - instant -> GraphQL `metricInstant(query, time?)`
308
+ - range -> GraphQL `metricRange(query, start, end, step)`
309
+ - **Note**: `metrics.query` routes PromQL through Fluent's GraphQL proxy. Raw Prometheus REST endpoints are not relied on directly by this tool.
310
+
311
+ Label hygiene reminder:
312
+ - `core_event_received_total` does **not** include `status`
313
+ - `rubix_event_runtime_seconds_*` does include `status`
314
+ - histogram bucket series add `le`
315
+
316
+ Accurate counter delta pattern for wider windows:
317
+
318
+ ```promql
319
+ (last_over_time(metric[window]) - metric offset <period>)
320
+ or
321
+ last_over_time(metric[window])
322
+ ```
323
+
324
+ Use the fallback `or` arm when offset samples are missing.
325
+
326
+ Instant example:
327
+
328
+ ```json
329
+ {
330
+ "query": "sum(rate(rubix_event_runtime_seconds_count[5m]))",
331
+ "type": "instant"
332
+ }
333
+ ```
334
+
335
+ Range example:
336
+
337
+ ```json
338
+ {
339
+ "query": "rate(rubix_event_runtime_seconds_count{status=\"FAILED\"}[5m])",
340
+ "type": "range",
341
+ "start": "2026-02-20T00:00:00Z",
342
+ "end": "2026-02-20T01:00:00Z",
343
+ "step": "1m"
344
+ }
345
+ ```
346
+
347
+ ## `metrics.topEvents`
348
+
349
+ Returns ranked event analytics for a time window by aggregating Event API results.
350
+
351
+ - **Required**: `from`
352
+ - **Optional**:
353
+ - `to`: end timestamp (defaults to now)
354
+ - `entityType`: filter to one entity type
355
+ - `eventType`: defaults to `ORCHESTRATION`
356
+ - `topN`: ranked rows to return (`1..100`, default `20`)
357
+ - `maxPages`: Event API pagination cap (`1..50`, default `10`)
358
+ - **Output summary**:
359
+ - `totalEvents`, `failureRate`, `statusBreakdown`
360
+ - `topEvents[]` grouped by `name + entityType + status`
361
+ - `uniqueEventNames`, `uniqueEntityTypes`
362
+
363
+ Example:
364
+
365
+ ```json
366
+ {
367
+ "from": "2026-02-22T00:00:00Z",
368
+ "to": "2026-02-22T12:00:00Z",
369
+ "topN": 20,
370
+ "eventType": "ORCHESTRATION"
371
+ }
372
+ ```
373
+
374
+ ## `metrics.healthCheck`
375
+
376
+ Runs a compact, one-call anomaly assessment.
377
+
378
+ - **Optional**:
379
+ - `window`: Prometheus/Event API analysis window (`1h`, `6h`, `24h`, `7d`; default `1h`)
380
+ - `includeTopEvents`: include ranked event list in response (default `true`)
381
+ - `topN`: max top event rows (`1..100`, default `10`)
382
+ - `thresholds`: override defaults
383
+ - `failureRate` (default `5`)
384
+ - `pendingRate` (default `10`)
385
+ - `dominanceRate` (default `50`)
386
+ - **Behavior**:
387
+ - Prometheus-first (`metrics.query` equivalent calls under the hood)
388
+ - Falls back to Event API aggregation if Prometheus is unavailable
389
+
390
+ Example:
391
+
392
+ ```json
393
+ {
394
+ "window": "1h",
395
+ "includeTopEvents": true,
396
+ "topN": 10
397
+ }
398
+ ```
399
+
400
+ ## `metrics.sloReport`
401
+
402
+ Returns a managed-services SLO snapshot with explicit KPI fields and threshold findings.
403
+
404
+ - **Optional**:
405
+ - `window`: KPI window (`30m`, `1h`, `24h`, `7d`; default `1h`)
406
+ - `includeTopFailingEvents`: include top failed events from Event API (default `true`)
407
+ - `topN`: top failed rows (`1..100`, default `10`)
408
+ - `maxPages`: Event API pagination cap for fallback/failed ranking (`1..50`, default `10`)
409
+ - `thresholds`: override defaults
410
+ - `failureRate` (default `5`)
411
+ - `noMatchRate` (default `0`)
412
+ - `pendingRate` (default `10`)
413
+ - `runtimeP95Seconds` (default `5`)
414
+ - `inflightP95Seconds` (default `60`)
415
+ - **Output highlights**:
416
+ - total/failed/no-match/pending event counts
417
+ - failure/no-match/pending rates
418
+ - p95 runtime and inflight latency (null when Prometheus fallback path is used)
419
+ - findings + recommendations + source (`prometheus` or `event_api`)
420
+
421
+ Example:
422
+
423
+ ```json
424
+ {
425
+ "window": "1h",
426
+ "includeTopFailingEvents": true,
427
+ "topN": 10
428
+ }
429
+ ```
430
+
431
+ ## `metrics.labelCatalog`
432
+
433
+ Discovers what labels a metric supports so PromQL groupings are correct.
434
+
435
+ - **Required**: `metric`
436
+ - **Optional**:
437
+ - `window`: live series sampling window (`30m`, `1h`, `24h`, `7d`; default `24h`)
438
+ - `includeKnownLabels`: include Fluent-known label hints (default `true`)
439
+ - `maxValuesPerLabel`: max sample values returned per label (`1..50`, default `10`)
440
+ - **Behavior**:
441
+ - runs `last_over_time(<metric>[window])` as an instant query
442
+ - extracts label keys from returned vector series
443
+ - reports presence/cardinality/sample values per label
444
+ - merges known Fluent label hints when available
445
+
446
+ Example:
447
+
448
+ ```json
449
+ {
450
+ "metric": "core_event_received_total",
451
+ "window": "1h",
452
+ "maxValuesPerLabel": 10
453
+ }
454
+ ```
455
+
456
+ ## `workflow.transitions`
457
+
458
+ Query available user actions (transitions) for entities at a given workflow state.
459
+
460
+ Calls `POST /api/v4.1/transition` to discover what events can be fired, what attributes they require, and how they appear in the UI.
461
+
462
+ - **Required**: `triggers` (array), each trigger requires `retailerId`
463
+ - **Optional per trigger**: `type`, `subtype`, `status`, `module`, `flexType`, `flexVersion`, `name`
464
+ - **Retailer behavior**: `retailerId` is required per trigger. Falls back to `FLUENT_RETAILER_ID` when omitted.
465
+ - **flexType auto-derive**: When `flexType` is not provided but `type` and `subtype` are, the tool auto-derives `flexType` as `{type}::{subtype}` (e.g., `CREDIT_MEMO::APPEASEMENT`). This avoids the silent empty-response problem where the Transition API returns `{"response":[]}` when `flexType` is missing.
466
+
467
+ ### Use cases
468
+
469
+ - Discover available actions at any workflow status without reading workflow JSON
470
+ - Build dynamic E2E test sequences that adapt to workflow changes
471
+ - Validate that expected user actions are available after deployment
472
+ - Get required event attributes for each action (avoids missing-attribute errors)
473
+
474
+ ### Integration with event.send
475
+
476
+ The `eventName` from each `userAction` maps directly to `event.send`'s `name` parameter. The `attributes[]` tell you what to include in `event.send`'s `attributes` parameter.
477
+
478
+ ### Example: ORDER at CREATED status
479
+
480
+ ```json
481
+ {
482
+ "triggers": [
483
+ {
484
+ "type": "ORDER",
485
+ "subtype": "HD",
486
+ "status": "CREATED",
487
+ "retailerId": "5",
488
+ "flexType": "ORDER::HD"
489
+ }
490
+ ]
491
+ }
492
+ ```
493
+
494
+ ### Example: MANIFEST with module filter
495
+
496
+ ```json
497
+ {
498
+ "triggers": [
499
+ {
500
+ "type": "MANIFEST",
501
+ "subtype": "DEFAULT",
502
+ "status": "PENDING",
503
+ "module": "servicepoint",
504
+ "flexType": "CARRIER::DEFAULT",
505
+ "retailerId": "2"
506
+ }
507
+ ]
508
+ }
509
+ ```
510
+
511
+ Typical success shape (truncated):
512
+
513
+ ```json
514
+ {
515
+ "ok": true,
516
+ "response": {
517
+ "response": [
518
+ {
519
+ "trigger": {
520
+ "type": "MANIFEST",
521
+ "subtype": "DEFAULT",
522
+ "status": "PENDING",
523
+ "module": "servicepoint",
524
+ "flexType": "CARRIER::DEFAULT",
525
+ "retailerId": "2"
526
+ },
527
+ "userActions": [
528
+ {
529
+ "eventName": "UPDATE",
530
+ "context": [
531
+ { "label": "SUBMIT", "type": "PRIMARY", "modules": ["servicepoint"], "confirm": false }
532
+ ],
533
+ "attributes": []
534
+ }
535
+ ]
536
+ }
537
+ ]
538
+ }
539
+ }
540
+ ```
541
+
542
+ ### Dynamic test sequence pattern
543
+
544
+ ```
545
+ 1. Create entity via graphql.query (mutation)
546
+ 2. workflow.transitions get available actions at current status
547
+ 3. For each action:
548
+ a. event.send with eventName and required attributes from response
549
+ b. Poll entity status until transition completes
550
+ c. workflow.transitions get next available actions
551
+ 4. Repeat until no more userActions (terminal state)
552
+ ```
553
+
554
+ ## `plugin.list`
555
+
556
+ List all registered orchestration rules (standard + custom) with metadata.
557
+
558
+ Calls `GET /orchestration/rest/v1/plugin` and returns a map keyed by fully-qualified rule name (e.g. `ACCOUNT.context.RuleName`).
559
+
560
+ - **Optional**: `name` (string) case-insensitive substring filter on rule keys
561
+ - **Requires**: SDK client with `request` method
562
+
563
+ ### Each entry contains
564
+
565
+ - `ruleInfo`: name, description, accepted entity types, produced events
566
+ - `eventAttributes`: attributes the rule reads from events
567
+ - `parameters`: configurable rule parameters (with types and descriptions)
568
+
569
+ ### Use cases
570
+
571
+ - Understand what rules do when analyzing workflows
572
+ - Discover all registered rules (standard + custom) for a given account
573
+ - Find rules by name pattern (e.g. all "SendEvent" variants)
574
+ - Cross-reference deployed rules with local source code for module validation
575
+
576
+ ### Example: list all rules
577
+
578
+ ```json
579
+ {}
580
+ ```
581
+
582
+ ### Example: filter by name
583
+
584
+ ```json
585
+ {
586
+ "name": "SendEvent"
587
+ }
588
+ ```
589
+
590
+ Typical success shape (truncated):
591
+
592
+ ```json
593
+ {
594
+ "ok": true,
595
+ "response": {
596
+ "FLUENTRETAIL.base.SendEvent": {
597
+ "ruleInfo": {
598
+ "name": "SendEvent",
599
+ "description": "Sends an event to a specified entity",
600
+ "accepts": ["ORDER", "FULFILMENT", "ARTICLE"],
601
+ "produces": ["SendEvent"]
602
+ },
603
+ "eventAttributes": [
604
+ { "name": "eventName", "type": "STRING", "description": "Name of the event to send" }
605
+ ],
606
+ "parameters": [
607
+ { "name": "eventName", "type": "STRING", "description": "Event name to dispatch" }
608
+ ]
609
+ }
610
+ }
611
+ }
612
+ ```
613
+
614
+ ## `graphql.query`
615
+
616
+ Executes a Fluent Commerce GraphQL query or mutation through the SDK.
617
+
618
+ - **Required**: `query`
619
+ - **Optional**: `variables`
620
+ - **Retry behavior**:
621
+ - query operations use configured retry/backoff
622
+ - mutation operations disable retries automatically to avoid duplicate writes
623
+
624
+ ### Basic Query
625
+
626
+ ```json
627
+ {
628
+ "query": "query { orders(first: 5) { edges { cursor node { id ref status } } pageInfo { hasNextPage } } }"
629
+ }
630
+ ```
631
+
632
+ ### Pagination
633
+
634
+ Fluent uses Relay-style connections. Cursors live on each **edge**, not on `pageInfo`. There is no `endCursor` or `startCursor`.
635
+
636
+ ```json
637
+ {
638
+ "query": "query($cursor: String) { orders(first: 50, after: $cursor) { edges { cursor node { id ref status } } pageInfo { hasNextPage } } }",
639
+ "variables": { "cursor": "Y3Vyc29yOi0tLTM2X18xNzcxMTc2MzMyMTc0" }
640
+ }
641
+ ```
642
+
643
+ To paginate: take the `cursor` from the **last edge** in the response, pass it as the `after` variable. Repeat while `hasNextPage` is `true`.
644
+
645
+ Pagination args: `first`/`after` (forward), `last`/`before` (backward).
646
+
647
+ ### Mutation
648
+
649
+ ```json
650
+ {
651
+ "query": "mutation($input: UpdateOrderInput!) { updateOrder(input: $input) { id ref status } }",
652
+ "variables": { "input": { "id": "36", "status": "RECEIVED" } }
653
+ }
654
+ ```
655
+
656
+ ### Synchronous Fulfilment Options (Live Checkout)
657
+
658
+ `graphql.query` supports synchronous fulfilment options orchestration calls,
659
+ commonly used by checkout journeys to get plans in the same response.
660
+
661
+ ```json
662
+ {
663
+ "query": "mutation CreateFulfilmentOption($retailerId: Int!, $type: String!, $orderType: String!, $ref: String!, $products: [CreateFulfilmentOptionProductInput!], $longitude: Float!, $latitude: Float!, $radius: Json!) { createFulfilmentOption(input: { retailerId: $retailerId, type: $type, orderType: $orderType, ref: $ref, products: $products, address: { city: \"Kellyville Ridge\", state: \"New South Wales\", country: \"Australia\", postcode: \"2155\", addressLine1: \"Kellyville Ridge NSW, Australia\", latitude: $latitude, longitude: $longitude }, attributes: { name: \"radius\", type: \"DOUBLE\", value: $radius } }, executionMode: AWAIT_ORCHESTRATION) { id createdOn plans { edges { node { ref status eta splitCount fulfilments { locationRef eta items { productRef availableQuantity requestedQuantity } } } } } } }",
664
+ "variables": {
665
+ "retailerId": 5,
666
+ "type": "CC",
667
+ "orderType": "CC",
668
+ "ref": "CHECKOUT-FO-12345",
669
+ "products": [
670
+ { "productRef": "MP09-34-Blue", "requestedQuantity": 1, "catalogueRef": "" }
671
+ ],
672
+ "longitude": 150.9193671,
673
+ "latitude": -33.7068442,
674
+ "radius": 50
675
+ }
676
+ }
677
+ ```
678
+
679
+ Notes:
680
+ - `executionMode: AWAIT_ORCHESTRATION` makes the call synchronous.
681
+ - The environment must contain a matching workflow for `type` (for example `FULFILMENT_OPTIONS::CC`).
682
+ - If no matching workflow exists, Fluent returns a backend workflow-not-found error.
683
+
684
+ ### Connection Shape
685
+
686
+ All list queries return connections:
687
+
688
+ ```graphql
689
+ {
690
+ edges {
691
+ cursor
692
+ node { ...fields }
693
+ }
694
+ pageInfo { hasNextPage }
695
+ }
696
+ ```
697
+
698
+ Common query roots: `orders`, `fulfilments`, `fulfilmentChoices`, `locations`, `inventoryPositions`, `products`, `categories`, `settings`, `waves`, `articles`.
699
+
700
+ ## `batch.create`
701
+
702
+ Creates a Fluent ingestion job.
703
+
704
+ - **Required**: `name`
705
+ - **Optional**: `retailerId`, `entityType`, `action`
706
+ - **Note**: `retailerId` falls back to `FLUENT_RETAILER_ID` if omitted
707
+
708
+ Example:
709
+
710
+ ```json
711
+ {
712
+ "name": "inventory-load-2026-02-06",
713
+ "entityType": "INVENTORY_POSITION",
714
+ "action": "UPSERT"
715
+ }
716
+ ```
717
+
718
+ ## `batch.send`
719
+
720
+ Sends payload records to an existing batch job.
721
+
722
+ - **Required**:
723
+ - `jobId`
724
+ - `payload.action`
725
+ - `payload.entityType`
726
+ - `payload.entities` (non-empty array)
727
+
728
+ Example:
729
+
730
+ ```json
731
+ {
732
+ "jobId": "JOB-123",
733
+ "payload": {
734
+ "action": "UPSERT",
735
+ "entityType": "INVENTORY_POSITION",
736
+ "entities": [
737
+ {
738
+ "locationRef": "LOC-1",
739
+ "skuRef": "SKU-1",
740
+ "qty": 10,
741
+ "type": "LAST_ON_HAND",
742
+ "status": "ACTIVE"
743
+ }
744
+ ]
745
+ }
746
+ }
747
+ ```
748
+
749
+ ## `batch.status`
750
+
751
+ Reads status for a previously created batch job.
752
+
753
+ - **Required**: `jobId`
754
+
755
+ Example:
756
+
757
+ ```json
758
+ {
759
+ "jobId": "JOB-123"
760
+ }
761
+ ```
762
+
763
+ ## `batch.batchStatus`
764
+
765
+ Gets the status of a specific batch within a job. Useful for troubleshooting partial failures in multi-batch jobs.
766
+
767
+ - **Required**: `jobId`, `batchId`
768
+
769
+ Example:
770
+
771
+ ```json
772
+ {
773
+ "jobId": "JOB-123",
774
+ "batchId": "BATCH-456"
775
+ }
776
+ ```
777
+
778
+ ## `batch.results`
779
+
780
+ Gets per-record outcomes for a completed job. Call after `batch.status` reports a terminal state.
781
+
782
+ - **Required**: `jobId`
783
+
784
+ Example:
785
+
786
+ ```json
787
+ {
788
+ "jobId": "JOB-123"
789
+ }
790
+ ```
791
+
792
+ ---
793
+
794
+ ## `graphql.queryAll`
795
+
796
+ Executes a GraphQL query with SDK auto-pagination. Automatically follows cursors, merges all edges, and deduplicates by node ID. Use instead of `graphql.query` when you need ALL records from a connection.
797
+
798
+ - **Required**: `query`
799
+ - **Optional**:
800
+ - `variables`: query variables (include cursor variable, usually `null` for first page)
801
+ - `maxPages`: max pages to fetch (default: 100, max: 500)
802
+ - `maxRecords`: max total records to accumulate (default: 10000, max: 50000)
803
+ - `timeoutMs`: hard timeout for entire pagination run (default: 300000ms = 5 min)
804
+ - `direction`: `forward` (first/after) or `backward` (last/before)
805
+ - `errorHandling`: `throw` (fail on errors) or `partial` (return partial data + errors)
806
+ - **Timeout behavior**:
807
+ - uses `timeoutMs` for the whole pagination run
808
+ - independent from `FLUENT_REQUEST_TIMEOUT_MS` single-call timeout
809
+
810
+ ### How it works
811
+
812
+ 1. SDK detects pagination variables (`first`/`after` or `last`/`before`) in your query
813
+ 2. Executes the first page
814
+ 3. Extracts cursor from the last edge, follows `hasNextPage`
815
+ 4. Merges edges across pages, deduplicates by node ID
816
+ 5. Stops when: no more pages, maxPages reached, maxRecords reached, or timeout
817
+
818
+ ### Response
819
+
820
+ Includes `extensions.autoPagination`:
821
+
822
+ ```json
823
+ {
824
+ "totalPages": 5,
825
+ "totalRecords": 487,
826
+ "truncated": false,
827
+ "direction": "forward"
828
+ }
829
+ ```
830
+
831
+ If truncated: `truncationReason` will be `"maxPages"`, `"maxRecords"`, or `"timeout"`.
832
+
833
+ ### Example: Fetch all active orders
834
+
835
+ ```json
836
+ {
837
+ "query": "query($cursor: String) { orders(first: 100, after: $cursor, status: \"ACTIVE\") { edges { cursor node { id ref status createdOn } } pageInfo { hasNextPage } } }",
838
+ "variables": { "cursor": null },
839
+ "maxRecords": 5000
840
+ }
841
+ ```
842
+
843
+ ### Example: Fetch all locations (backward)
844
+
845
+ ```json
846
+ {
847
+ "query": "query($cursor: String) { locations(last: 50, before: $cursor) { edges { cursor node { id ref name type } } pageInfo { hasPreviousPage } } }",
848
+ "variables": { "cursor": null },
849
+ "direction": "backward"
850
+ }
851
+ ```
852
+
853
+ ## `graphql.batchMutate`
854
+
855
+ Executes multiple GraphQL mutations in a single request using aliased mutations. Sends up to 50 mutations at once, each with its own input.
856
+
857
+ - **Required**: `mutation`, `inputs`
858
+ - **Optional**: `returnFields`, `operationName`
859
+ - **Retry behavior**: disabled (non-idempotent write operation)
860
+
861
+ ### How it works
862
+
863
+ 1. Builds an aliased mutation query:
864
+ ```graphql
865
+ mutation BatchUpdateOrders($input1: UpdateOrderInput!, $input2: UpdateOrderInput!) {
866
+ updateOrder1: updateOrder(input: $input1) { id ref status }
867
+ updateOrder2: updateOrder(input: $input2) { id ref status }
868
+ }
869
+ ```
870
+ 2. Sends as a single GraphQL request
871
+ 3. Parses per-mutation results (success/failure for each input)
872
+
873
+ ### Response
874
+
875
+ ```json
876
+ {
877
+ "ok": true,
878
+ "summary": "All 3 mutations succeeded",
879
+ "executed": 3,
880
+ "failed": 0,
881
+ "allSucceeded": true,
882
+ "allFailed": false,
883
+ "results": [
884
+ { "alias": "updateOrder1", "index": 0, "success": true, "data": { "id": "1", "ref": "ORD-001", "status": "SHIPPED" } },
885
+ { "alias": "updateOrder2", "index": 1, "success": true, "data": { "id": "2", "ref": "ORD-002", "status": "SHIPPED" } }
886
+ ]
887
+ }
888
+ ```
889
+
890
+ On partial failure, `errors` array includes per-mutation error details with `alias`, `index`, `message`, and `inputRef`.
891
+
892
+ ### Example: Bulk update order statuses
893
+
894
+ ```json
895
+ {
896
+ "mutation": "updateOrder",
897
+ "inputs": [
898
+ { "id": "36", "status": "SHIPPED" },
899
+ { "id": "37", "status": "SHIPPED" },
900
+ { "id": "38", "status": "SHIPPED" }
901
+ ],
902
+ "returnFields": ["id", "ref", "status"]
903
+ }
904
+ ```
905
+
906
+ ### Example: Batch create inventory positions
907
+
908
+ ```json
909
+ {
910
+ "mutation": "updateInventoryPosition",
911
+ "inputs": [
912
+ { "id": "100", "status": "ACTIVE" },
913
+ { "id": "101", "status": "ACTIVE" }
914
+ ],
915
+ "returnFields": ["id", "ref", "status", "onHand"]
916
+ }
917
+ ```
918
+
919
+ ## `graphql.introspect`
920
+
921
+ Inspects the Fluent Commerce GraphQL schema via introspection. Fetches the full schema and caches it for 1 hour. Use to discover mutations, input types, and field requirements at runtime.
922
+
923
+ - **Optional** (specify one mode):
924
+ - `type`: name of an INPUT_OBJECT type to inspect (e.g., `UpdateOrderInput`)
925
+ - `mutation`: name of a mutation to inspect (e.g., `updateOrder`)
926
+ - `listMutations`: `true` to list all available mutation names
927
+ - `listQueries`: `true` to list all available query root field names
928
+
929
+ ### Example: List all mutations
930
+
931
+ ```json
932
+ {
933
+ "listMutations": true
934
+ }
935
+ ```
936
+
937
+ Response:
938
+
939
+ ```json
940
+ {
941
+ "ok": true,
942
+ "mutations": ["createOrder", "updateOrder", "createFulfilment", "..."],
943
+ "count": 142
944
+ }
945
+ ```
946
+
947
+ ### Example: Inspect a mutation
948
+
949
+ ```json
950
+ {
951
+ "mutation": "updateOrder"
952
+ }
953
+ ```
954
+
955
+ Response:
956
+
957
+ ```json
958
+ {
959
+ "ok": true,
960
+ "mutation": {
961
+ "name": "updateOrder",
962
+ "description": "Updates an existing order",
963
+ "args": [
964
+ {
965
+ "name": "input",
966
+ "type": "UpdateOrderInput!",
967
+ "required": true
968
+ }
969
+ ],
970
+ "returnType": "Order"
971
+ }
972
+ }
973
+ ```
974
+
975
+ ### Example: Inspect an input type
976
+
977
+ ```json
978
+ {
979
+ "type": "UpdateOrderInput"
980
+ }
981
+ ```
982
+
983
+ Response:
984
+
985
+ ```json
986
+ {
987
+ "ok": true,
988
+ "inputType": {
989
+ "name": "UpdateOrderInput",
990
+ "fields": [
991
+ { "name": "id", "type": "ID!", "required": true },
992
+ { "name": "ref", "type": "String", "required": false },
993
+ { "name": "status", "type": "String", "required": false },
994
+ { "name": "attributes", "type": "[AttributeInput]", "required": false, "isArray": true }
995
+ ]
996
+ }
997
+ }
998
+ ```
999
+
1000
+ ### Chaining example
1001
+
1002
+ 1. `{ "listMutations": true }` → find `updateOrder`
1003
+ 2. `{ "mutation": "updateOrder" }` → see it takes `UpdateOrderInput!`
1004
+ 3. `{ "type": "UpdateOrderInput" }` see all updatable fields
1005
+ 4. Use `graphql.query` or `graphql.batchMutate` with the discovered schema
1006
+
1007
+ ## `connection.test`
1008
+
1009
+ Comprehensive Fluent Commerce connectivity test. Authenticates, executes a `me` query, and returns the authenticated user's details.
1010
+
1011
+ - **Input**: no fields
1012
+
1013
+ ### Response (success)
1014
+
1015
+ ```json
1016
+ {
1017
+ "ok": true,
1018
+ "duration": 342,
1019
+ "details": {
1020
+ "userId": "12",
1021
+ "username": "admin@hmtest.com",
1022
+ "userType": "RETAILER",
1023
+ "userStatus": "ACTIVE",
1024
+ "retailerId": "5",
1025
+ "retailerRef": "HMTEST",
1026
+ "retailerName": "HM Test",
1027
+ "locationId": "10",
1028
+ "locationRef": "W252",
1029
+ "locationName": "Warehouse 252"
1030
+ }
1031
+ }
1032
+ ```
1033
+
1034
+ ### Response (failure)
1035
+
1036
+ ```json
1037
+ {
1038
+ "ok": false,
1039
+ "duration": 1500,
1040
+ "error": "Authentication failed after 3 retries: 401 Unauthorized"
1041
+ }
1042
+ ```
1043
+
1044
+ More thorough than `health.ping` — actually verifies the GraphQL endpoint works end-to-end. Use when first connecting, debugging auth issues, or verifying retailer/location context.
1045
+
1046
+ ## `webhook.validate`
1047
+
1048
+ Validates a Fluent Commerce webhook payload and optionally verifies its signature.
1049
+
1050
+ - **Required**: `payload`
1051
+ - **Optional**:
1052
+ - `rawBody`: exact original HTTP request body string for signature checks
1053
+ - `signature`: the `X-Fluent-Signature` header value (base64-encoded)
1054
+ - `publicKey`: the Fluent public key (PEM format) for signature verification
1055
+ - `algorithm`: `SHA512withRSA` (default) or `MD5withRSA`
1056
+
1057
+ ### Mode 1: Basic validation
1058
+
1059
+ Checks that required fields (`name`, `id`, `retailerId`) are present in the payload.
1060
+
1061
+ ```json
1062
+ {
1063
+ "payload": {
1064
+ "name": "OrderCreated",
1065
+ "id": "event-123",
1066
+ "retailerId": "5",
1067
+ "entityType": "ORDER",
1068
+ "entityRef": "ORD-001"
1069
+ }
1070
+ }
1071
+ ```
1072
+
1073
+ Response:
1074
+
1075
+ ```json
1076
+ {
1077
+ "ok": true,
1078
+ "basicValidation": { "valid": true, "missingFields": [] },
1079
+ "note": "Basic field validation passed. Provide signature + publicKey for signature verification."
1080
+ }
1081
+ ```
1082
+
1083
+ ### Mode 2: Signature validation
1084
+
1085
+ Verifies the webhook body against the `X-Fluent-Signature` header using the provided public key.
1086
+ Use `rawBody` whenever possible because signature verification is byte-sensitive.
1087
+
1088
+ ```json
1089
+ {
1090
+ "payload": {
1091
+ "name": "OrderCreated",
1092
+ "id": "event-123",
1093
+ "retailerId": "5"
1094
+ },
1095
+ "rawBody": "{\"name\":\"OrderCreated\",\"id\":\"event-123\",\"retailerId\":\"5\"}",
1096
+ "signature": "base64-encoded-signature-from-header",
1097
+ "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----",
1098
+ "algorithm": "SHA512withRSA"
1099
+ }
1100
+ ```
1101
+
1102
+ Response:
1103
+
1104
+ ```json
1105
+ {
1106
+ "ok": true,
1107
+ "basicValidation": { "valid": true, "missingFields": [] },
1108
+ "signatureValidation": {
1109
+ "valid": true,
1110
+ "algorithm": "SHA512withRSA",
1111
+ "payloadSource": "rawBody"
1112
+ }
1113
+ }
1114
+ ```
1115
+
1116
+ ---
1117
+
1118
+ ## `entity.create`
1119
+
1120
+ Type-safe entity creation with built-in validation and gotcha knowledge.
1121
+
1122
+ **Supported entity types:** ORDER, FULFILMENT, LOCATION, NETWORK, CUSTOMER, PRODUCT, INVENTORY_POSITION, VIRTUAL_CATALOGUE, VIRTUAL_POSITION, CATEGORY, CARRIER, SETTING
1123
+
1124
+ - **Required**:
1125
+ - `entityType`: entity type string (see supported list above)
1126
+ - `data`: creation input fields matching the GraphQL create input type
1127
+ - **Optional**:
1128
+ - `returnFields`: array of field names to return (defaults to entity type defaults)
1129
+ - `dryRun`: if `true`, builds and validates the mutation without executing (default `false`)
1130
+
1131
+ **Key behaviors:**
1132
+
1133
+ - Validates required fields BEFORE sending (e.g., Location requires `openingSchedule`)
1134
+ - Encodes compound key rules (ProductKey needs `ref` + `catalogue.ref`)
1135
+ - Auto-resolves `retailerId` from config for retailer-scoped entities
1136
+ - Returns the executed mutation string for audit trail
1137
+
1138
+ **Known gotchas (encoded in validation):**
1139
+
1140
+ - No create inputs have a `status` field status is auto-set to `CREATED`
1141
+ - `Customer` has NO `ref` field; `username` is the identifier
1142
+ - `Network` uses `name` (not `ref`) in create; `retailers` is a plural array
1143
+ - `Location` requires `openingSchedule` even for 24/7 (use `allHours: true`)
1144
+ - `Product` `gtin` has a 20-character max
1145
+ - `Setting` `context` is a plain String, `contextId` is a separate Int
1146
+
1147
+ **Example** create a location (dry run):
1148
+
1149
+ ```json
1150
+ {
1151
+ "entityType": "LOCATION",
1152
+ "data": {
1153
+ "ref": "LOC_WH_01",
1154
+ "type": "WAREHOUSE",
1155
+ "name": "Main Warehouse",
1156
+ "openingSchedule": { "allHours": true }
1157
+ },
1158
+ "dryRun": true
1159
+ }
1160
+ ```
1161
+
1162
+ Response (dry run):
1163
+
1164
+ ```json
1165
+ {
1166
+ "ok": true,
1167
+ "dryRun": true,
1168
+ "entityType": "LOCATION",
1169
+ "mutation": "mutation CreateLocation($input: CreateLocationInput!) { createLocation(input: $input) { id ref status type name } }",
1170
+ "inputType": "CreateLocationInput",
1171
+ "variables": { "input": { "ref": "LOC_WH_01", "type": "WAREHOUSE", "name": "Main Warehouse", "openingSchedule": { "allHours": true } } },
1172
+ "requiredFields": ["ref", "type", "name", "openingSchedule"],
1173
+ "gotchas": ["Location requires openingSchedule — use { allHours: true } for 24/7"],
1174
+ "note": "No API call made. Set dryRun=false to execute."
1175
+ }
1176
+ ```
1177
+
1178
+ **Example** — create a location (real):
1179
+
1180
+ ```json
1181
+ {
1182
+ "entityType": "LOCATION",
1183
+ "data": {
1184
+ "ref": "LOC_WH_01",
1185
+ "type": "WAREHOUSE",
1186
+ "name": "Main Warehouse",
1187
+ "openingSchedule": { "allHours": true }
1188
+ }
1189
+ }
1190
+ ```
1191
+
1192
+ Response:
1193
+
1194
+ ```json
1195
+ {
1196
+ "ok": true,
1197
+ "entityType": "LOCATION",
1198
+ "entity": { "id": "42", "ref": "LOC_WH_01", "status": "CREATED", "type": "WAREHOUSE", "name": "Main Warehouse" },
1199
+ "mutation": "mutation CreateLocation($input: CreateLocationInput!) { ... }"
1200
+ }
1201
+ ```
1202
+
1203
+ ---
1204
+
1205
+ ## `entity.update`
1206
+
1207
+ Status-aware entity updates with optional transition validation.
1208
+
1209
+ - **Required**:
1210
+ - `entityType`: entity type string
1211
+ - `id`: entity ID (required for updates)
1212
+ - `fields`: object of fields to update (matches GraphQL update input type)
1213
+ - **Optional**:
1214
+ - `returnFields`: array of field names to return
1215
+ - `validateTransition`: if `true` and `status` is being changed, queries `workflow.transitions` to verify the transition is allowed before executing (default `false`)
1216
+
1217
+ **Key behaviors:**
1218
+
1219
+ - Auto-detects the correct GraphQL mutation name from entity type
1220
+ - When `validateTransition` is `true`: fetches current entity state, queries transition API, warns if no workflow transitions exist from the current status
1221
+ - Returns the previous status for audit trail when transition validation is used
1222
+ - Uses no-retry semantics (write operation)
1223
+
1224
+ **Example** — update order status with transition validation:
1225
+
1226
+ ```json
1227
+ {
1228
+ "entityType": "ORDER",
1229
+ "id": "12345",
1230
+ "fields": { "status": "COMPLETE" },
1231
+ "validateTransition": true
1232
+ }
1233
+ ```
1234
+
1235
+ Response:
1236
+
1237
+ ```json
1238
+ {
1239
+ "ok": true,
1240
+ "entityType": "ORDER",
1241
+ "entity": { "id": "12345", "ref": "HD-001", "status": "COMPLETE" },
1242
+ "mutation": "mutation UpdateOrder($input: UpdateOrderInput!) { ... }"
1243
+ }
1244
+ ```
1245
+
1246
+ If no workflow transition exists:
1247
+
1248
+ ```json
1249
+ {
1250
+ "ok": true,
1251
+ "entityType": "ORDER",
1252
+ "entity": { "id": "12345", "ref": "HD-001", "status": "COMPLETE" },
1253
+ "mutation": "...",
1254
+ "transitionWarning": "No workflow transitions available from status \"BOOKED\" for ORDER. The status update may succeed as a direct mutation but will not trigger workflow orchestration."
1255
+ }
1256
+ ```
1257
+
1258
+ ---
1259
+
1260
+ ## `entity.get`
1261
+
1262
+ Unified entity lookup by ID or ref with optional edge inclusion.
1263
+
1264
+ - **Required**:
1265
+ - `entityType`: entity type string
1266
+ - At least one of `id` or `ref`
1267
+ - **Optional**:
1268
+ - `id`: entity ID (preferred lookup method)
1269
+ - `ref`: entity ref (fallback — not available for CUSTOMER)
1270
+ - `fields`: array of field names to return (defaults to entity type defaults: id, ref, status, type, createdOn, etc.)
1271
+ - `includeEdges`: array of related entity edges to fetch (e.g., `["fulfilments", "items", "attributes"]`)
1272
+
1273
+ **Key behaviors:**
1274
+
1275
+ - Prefers ID-based lookup (single entity query) over ref-based lookup (connection query)
1276
+ - Ref-based lookup uses the connection query pattern (edges/node)
1277
+ - Returns `{ found: false }` when entity is not found (not an error)
1278
+ - CUSTOMER entity type does not support ref-based lookup
1279
+
1280
+ **Example** — get order by ref with fulfilments:
1281
+
1282
+ ```json
1283
+ {
1284
+ "entityType": "ORDER",
1285
+ "ref": "HD-001",
1286
+ "includeEdges": ["fulfilments"]
1287
+ }
1288
+ ```
1289
+
1290
+ Response:
1291
+
1292
+ ```json
1293
+ {
1294
+ "ok": true,
1295
+ "found": true,
1296
+ "entityType": "ORDER",
1297
+ "entity": {
1298
+ "id": "12345",
1299
+ "ref": "HD-001",
1300
+ "status": "BOOKED",
1301
+ "type": "HD",
1302
+ "fulfilments": {
1303
+ "edges": [
1304
+ { "node": { "id": "67890", "ref": "FUL-001", "status": "CREATED" } }
1305
+ ]
1306
+ }
1307
+ }
1308
+ }
1309
+ ```
1310
+
1311
+ ---
1312
+
1313
+ ## `workflow.get`
1314
+
1315
+ Fetch a specific workflow definition by entity type and subtype via REST API.
1316
+
1317
+ Uses `GET /api/v4.1/workflow/{retailerId}/{entityType}::{entitySubtype}/` — works even when the list endpoint returns 401 (permission-restricted accounts).
1318
+
1319
+ - **Required**:
1320
+ - `entityType`: workflow entity type (e.g., `ORDER`, `FULFILMENT`, `INVENTORY_CATALOGUE`)
1321
+ - `entitySubtype`: workflow entity subtype (e.g., `HD`, `DEFAULT`, `MASTER`)
1322
+ - **Optional**:
1323
+ - `retailerId`: target retailer ID (falls back to `FLUENT_RETAILER_ID`)
1324
+ - `version`: specific workflow version to fetch (omit for latest)
1325
+ - `outputFile`: file path to save workflow JSON. When set, writes full workflow to file and returns only summary (name, version, status/ruleset/rule counts) — keeps large workflow JSON out of the LLM context. Parent directories are created automatically.
1326
+
1327
+ **Response** includes a summary (status count, ruleset count, total rules) plus the full workflow JSON (unless `outputFile` is set).
1328
+
1329
+ **Known entity types:** ORDER, FULFILMENT, FULFILMENT_CHOICE, ARTICLE, RETURN_ORDER, WAVE, CONSIGNMENT, INVENTORY_CATALOGUE, INVENTORY_POSITION, VIRTUAL_CATALOGUE, LOCATION, PRODUCT, CUSTOMER, CARRIER, NETWORK, CREDIT_MEMO.
1330
+
1331
+ **Example:**
1332
+
1333
+ ```json
1334
+ {
1335
+ "entityType": "INVENTORY_CATALOGUE",
1336
+ "entitySubtype": "DEFAULT",
1337
+ "retailerId": "34"
1338
+ }
1339
+ ```
1340
+
1341
+ **Note:** Fluent returns HTTP 200 with an empty body for nonexistent workflows — the handler checks for meaningful content (presence of `name` field) and returns a clear "not found" message.
1342
+
1343
+ ---
1344
+
1345
+ ## `workflow.list`
1346
+
1347
+ List all workflows for a retailer via REST API.
1348
+
1349
+ Uses `GET /api/v4.1/workflow?retailerId={id}&count=200`. Response is deduplicated to show only the latest version of each workflow. Reads from **live server**, NOT the workflowlog cache.
1350
+
1351
+ - **Optional**:
1352
+ - `retailerId`: target retailer ID (falls back to `FLUENT_RETAILER_ID`)
1353
+ - `outputDir`: directory path to download ALL workflows (latest version each) as individual JSON files. Each file is named `<TYPE>-<SUBTYPE>.json`. Returns only summary metadata when set — full JSON stays on disk. Parent directories are created automatically.
1354
+
1355
+ **Response** includes per-workflow summary (name, version, status count, ruleset count) and totals.
1356
+
1357
+ **Example:**
1358
+
1359
+ ```json
1360
+ {
1361
+ "retailerId": "35"
1362
+ }
1363
+ ```
1364
+
1365
+ **Note:** Some accounts restrict this endpoint (401 "Incorrect retailer"). In that case, use `workflow.get` with a specific entityType + entitySubtype instead.
1366
+
1367
+ ---
1368
+
1369
+ ## `workflow.upload`
1370
+
1371
+ Deploy a workflow JSON definition to the Fluent environment.
1372
+
1373
+ Uploads via REST API `POST /api/v4.1/workflow/{retailerId}` — the same endpoint the Fluent CLI uses.
1374
+
1375
+ - **Required**:
1376
+ - `workflow`: workflow JSON definition (as object or JSON string)
1377
+ - **Optional**:
1378
+ - `retailerId`: target retailer ID (falls back to `FLUENT_RETAILER_ID`)
1379
+ - `validate`: validate structure before uploading (default `true`)
1380
+ - `dryRun`: validate only, do not deploy (default `false`)
1381
+
1382
+ **Validation checks:**
1383
+
1384
+ - `name` field present
1385
+ - At least one status defined in `statuses` array
1386
+ - All rulesets have `name`, `triggers`, and `rules` (warns if empty)
1387
+
1388
+ **Important:** For production deployments, prefer `fluent module install` via CLI which bundles workflows with settings, rules, and data in a versioned module. Use this tool for interactive editing, hotfixes, or when CLI is unavailable.
1389
+
1390
+ **Example** — dry-run validation:
1391
+
1392
+ ```json
1393
+ {
1394
+ "workflow": {
1395
+ "name": "ORDER::HD",
1396
+ "type": "ORDER",
1397
+ "subtype": "HD",
1398
+ "statuses": [{ "name": "CREATED" }, { "name": "BOOKED" }],
1399
+ "rulesets": [
1400
+ {
1401
+ "name": "BookOrder",
1402
+ "triggers": [{ "status": "CREATED" }],
1403
+ "rules": [{ "name": "com.fluentretail.rubix.rule.order.SendEventOnVerifyingState", "props": { "status": "BOOKED" } }]
1404
+ }
1405
+ ]
1406
+ },
1407
+ "dryRun": true
1408
+ }
1409
+ ```
1410
+
1411
+ Response (dry run — valid):
1412
+
1413
+ ```json
1414
+ {
1415
+ "ok": true,
1416
+ "dryRun": true,
1417
+ "valid": true,
1418
+ "workflowName": "ORDER::HD",
1419
+ "retailerId": "5",
1420
+ "note": "Validation passed. Set dryRun=false to deploy."
1421
+ }
1422
+ ```
1423
+
1424
+ ---
1425
+
1426
+ ## `workflow.diff`
1427
+
1428
+ Compare two workflow JSON definitions and identify changes.
1429
+
1430
+ Pure local computation — no API calls.
1431
+
1432
+ - **Required**:
1433
+ - `base`: base workflow JSON (before changes)
1434
+ - `target`: target workflow JSON (after changes)
1435
+ - **Optional**:
1436
+ - `format`: output format — `summary` (default), `detailed`, or `mermaid`
1437
+
1438
+ **Comparison scope:**
1439
+
1440
+ - Rulesets: added, removed, modified (trigger changes, rule additions/removals, prop changes)
1441
+ - Statuses: added, removed
1442
+ - Risk assessment: removing rulesets or statuses = HIGH, modifying props = MEDIUM, adding = LOW
1443
+
1444
+ **Formats:**
1445
+
1446
+ | Format | Returns |
1447
+ |---|---|
1448
+ | `summary` | Change counts, status changes, risk level |
1449
+ | `detailed` | Full `WorkflowDiffResult` object with per-ruleset breakdown |
1450
+ | `mermaid` | `stateDiagram-v2` with color-coded added/removed/modified states and transitions |
1451
+
1452
+ **Example** — summary diff:
1453
+
1454
+ ```json
1455
+ {
1456
+ "base": { "name": "ORDER::HD", "statuses": [...], "rulesets": [...] },
1457
+ "target": { "name": "ORDER::HD", "statuses": [...], "rulesets": [...] },
1458
+ "format": "summary"
1459
+ }
1460
+ ```
1461
+
1462
+ Response:
1463
+
1464
+ ```json
1465
+ {
1466
+ "ok": true,
1467
+ "summary": "3 ruleset change(s): 1 added, 0 removed, 2 modified. 1 status change(s): 1 added, 0 removed. Risk level: MEDIUM.",
1468
+ "riskLevel": "medium",
1469
+ "rulesetChanges": { "added": 1, "removed": 0, "modified": 2 },
1470
+ "statusChanges": { "added": ["AWAITING_PICKUP"], "removed": [] }
1471
+ }
1472
+ ```
1473
+
1474
+ ---
1475
+
1476
+ ## `workflow.simulate`
1477
+
1478
+ Static prediction of workflow event outcomes from workflow JSON.
1479
+
1480
+ Pure local computation — no API calls. Parses workflow JSON to find matching rulesets and predict state transitions, follow-on events, and webhook side effects.
1481
+
1482
+ - **Required**:
1483
+ - `workflow`: workflow JSON definition (as object or JSON string)
1484
+ - `currentStatus`: entity status to simulate from (e.g., `CREATED`, `BOOKED`)
1485
+ - **Optional**:
1486
+ - `eventName`: event name to match against ruleset names (if omitted, returns all rulesets triggered by `currentStatus`)
1487
+ - `entityType`: entity type filter for trigger matching
1488
+ - `entitySubtype`: entity subtype filter for trigger matching
1489
+
1490
+ **What it does:**
1491
+
1492
+ - Finds rulesets whose triggers match `currentStatus` (and optionally `eventName`)
1493
+ - Extracts `SetState`/`ChangeState` rules → predicted next status
1494
+ - Extracts `SendEvent` rules → predicted follow-on events
1495
+ - Extracts `SendWebhook` rules → predicted webhook side effects
1496
+ - Identifies custom Java rules that cannot be statically predicted
1497
+
1498
+ **Important limitations (always disclosed in response):**
1499
+
1500
+ - **STATIC ONLY** — cannot evaluate runtime conditions, entity attributes, or settings values
1501
+ - **CUSTOM RULES OPAQUE** — Java plugin rules may conditionally execute; behavior unknown without live state
1502
+ - **NO CROSS-ENTITY** — does not follow SendEvent chains into child entity workflows
1503
+ - Use `workflow.transitions` for **authoritative live validation** of available actions
1504
+
1505
+ **Example** — simulate from CREATED status:
1506
+
1507
+ ```json
1508
+ {
1509
+ "workflow": { "name": "ORDER::HD", "rulesets": [...] },
1510
+ "currentStatus": "CREATED",
1511
+ "eventName": "BookOrder"
1512
+ }
1513
+ ```
1514
+
1515
+ Response:
1516
+
1517
+ ```json
1518
+ {
1519
+ "ok": true,
1520
+ "workflowName": "ORDER::HD",
1521
+ "currentStatus": "CREATED",
1522
+ "eventName": "BookOrder",
1523
+ "matchedRulesets": 1,
1524
+ "rulesets": [
1525
+ {
1526
+ "name": "BookOrder",
1527
+ "matchType": "statusAndEvent",
1528
+ "predictedStatus": "BOOKED",
1529
+ "predictedEvents": ["SendToFulfilment"],
1530
+ "predictedWebhooks": [],
1531
+ "ruleCount": 3
1532
+ }
1533
+ ],
1534
+ "prediction": {
1535
+ "likelyNextStatus": "BOOKED",
1536
+ "followOnEvents": ["SendToFulfilment"],
1537
+ "webhookSideEffects": [],
1538
+ "confidence": "low",
1539
+ "note": "Custom rules present — prediction may be incomplete or wrong."
1540
+ },
1541
+ "limitations": [
1542
+ "STATIC PREDICTION ONLY — does not account for runtime conditions, entity attributes, or settings values.",
1543
+ "1 custom rule(s) found whose behavior cannot be predicted statically: ForwardIfOrderCoordinatesPresent.",
1544
+ "Use workflow.transitions for AUTHORITATIVE live validation of available actions."
1545
+ ]
1546
+ }
1547
+ ```
1548
+
1549
+ ---
1550
+
1551
+ ## `setting.get`
1552
+
1553
+ Fetch one or more Fluent Commerce settings by name (supports `%` wildcards).
1554
+
1555
+ Uses the GraphQL `settings(name:)` query. Returns metadata inline (name, context, contextId, valueType). For large settings (manifests, JSON > 4KB), use `outputFile` to save content to a local file and keep it out of the LLM context.
1556
+
1557
+ - **Required**:
1558
+ - `name`: setting name or pattern. Supports `%` wildcards (e.g., `fc.mystique.manifest.%` for all manifest settings)
1559
+ - **Optional**:
1560
+ - `context`: filter by context — `ACCOUNT`, `RETAILER`, etc.
1561
+ - `contextId`: context ID. Falls back to `FLUENT_RETAILER_ID` for `RETAILER` context, `0` for `ACCOUNT`.
1562
+ - `outputFile`: file path to save the setting value/lobValue. When provided, writes content to file and returns only metadata (name, context, valueType, size) — keeps large manifests out of the LLM context. Parent directories are created automatically.
1563
+ - `first`: max results (default: 10, max: 100)
1564
+
1565
+ **Behavior with `outputFile`:**
1566
+
1567
+ - **Single match** + `outputFile` → writes lobValue (or value) to the file, returns metadata + file path + byte size (NOT the content)
1568
+ - **Multiple matches** + `outputFile` → writes each setting to a separate file in the directory (named `<setting-name>.json`), returns per-file metadata
1569
+ - **No `outputFile`** → returns full setting data inline (may be large for manifests)
1570
+ - JSON content is automatically pretty-printed when saved to file
1571
+
1572
+ **Example** — fetch a specific manifest setting to disk:
1573
+
1574
+ ```json
1575
+ {
1576
+ "name": "fc.mystique.manifest.oms",
1577
+ "context": "ACCOUNT",
1578
+ "outputFile": "./accounts/SAGIRISH/manifests/backups/fc.mystique.manifest.oms.json"
1579
+ }
1580
+ ```
1581
+
1582
+ **Example** — list all manifest settings (wildcard):
1583
+
1584
+ ```json
1585
+ {
1586
+ "name": "fc.mystique.manifest.%",
1587
+ "context": "ACCOUNT"
1588
+ }
1589
+ ```
1590
+
1591
+ **Example** — download all manifests to directory:
1592
+
1593
+ ```json
1594
+ {
1595
+ "name": "fc.mystique.manifest.%",
1596
+ "outputFile": "./accounts/SAGIRISH/manifests/backups/"
1597
+ }
1598
+ ```
1599
+
1600
+ ---
1601
+
1602
+ ## `setting.upsert`
1603
+
1604
+ Create or update a Fluent Commerce setting with upsert semantics.
1605
+
1606
+ Queries existing settings by `name` + `context` + `contextId` first. Creates if missing, updates if exists.
1607
+
1608
+ - **Required**:
1609
+ - `name`: setting key/name
1610
+ - `value`: setting value (for small values)
1611
+ - `context`: setting scope — `RETAILER`, `ACCOUNT`, `LOCATION`, `NETWORK`, `AGENT`, `CUSTOMER`
1612
+ - **Optional**:
1613
+ - `lobValue`: large object value for JSON payloads > 4KB (mutually exclusive with `value`)
1614
+ - `contextId`: context ID (e.g., retailer ID). Defaults to `FLUENT_RETAILER_ID` for `RETAILER` context.
1615
+ - `valueType`: explicit value type — `"STRING"`, `"LOB"`, or `"JSON"`. **Use `"JSON"` for Mystique manifest settings** (`fc.mystique.*`). Defaults to `"LOB"` when `lobValue` is provided, `"STRING"` when `value` is provided.
1616
+
1617
+ **Key gotchas:**
1618
+
1619
+ - `context` is a plain String (`"RETAILER"`), NOT an object
1620
+ - `contextId` is a separate Int field
1621
+ - For large JSON values (> 4KB), use `lobValue` instead of `value`
1622
+ - **For Mystique manifest settings, set `valueType: "JSON"`** — without it, defaults to `"LOB"` which breaks manifest parsing. The tool emits a `warning` field in the response when a `fc.mystique.manifest.*` name is used without `valueType: "JSON"`.
1623
+ - Returns `created: true/false` for audit trail
1624
+
1625
+ **Example** — create a webhook URL setting:
1626
+
1627
+ ```json
1628
+ {
1629
+ "name": "WEBHOOK_ORDER_NOTIFICATION",
1630
+ "value": "https://api.example.com/webhooks/orders",
1631
+ "context": "RETAILER"
1632
+ }
1633
+ ```
1634
+
1635
+ Response (created):
1636
+
1637
+ ```json
1638
+ {
1639
+ "ok": true,
1640
+ "created": true,
1641
+ "updated": false,
1642
+ "setting": {
1643
+ "id": "99",
1644
+ "name": "WEBHOOK_ORDER_NOTIFICATION",
1645
+ "value": "https://api.example.com/webhooks/orders",
1646
+ "context": "RETAILER",
1647
+ "contextId": 5
1648
+ }
1649
+ }
1650
+ ```
1651
+
1652
+ Response (updated existing):
1653
+
1654
+ ```json
1655
+ {
1656
+ "ok": true,
1657
+ "created": false,
1658
+ "updated": true,
1659
+ "setting": { "id": "99", "name": "WEBHOOK_ORDER_NOTIFICATION", "value": "https://api.example.com/webhooks/orders-v2", "context": "RETAILER", "contextId": 5 },
1660
+ "previousValue": "https://api.example.com/webhooks/orders"
1661
+ }
1662
+ ```
1663
+
1664
+ ---
1665
+
1666
+ ## `setting.bulkUpsert`
1667
+
1668
+ Batch create or update multiple settings in one call.
1669
+
1670
+ Processes up to 50 settings sequentially with individual error handling. Each setting is an independent upsert — failures on one setting don't block others.
1671
+
1672
+ - **Required**:
1673
+ - `settings`: array of setting objects (min 1, max 50), each with `name`, `value`, `context`, and optional `lobValue`, `contextId`, and `valueType` (`"STRING"`, `"LOB"`, or `"JSON"`)
1674
+
1675
+ **Example** — bulk create 3 settings:
1676
+
1677
+ ```json
1678
+ {
1679
+ "settings": [
1680
+ { "name": "WEBHOOK_ORDER_URL", "value": "https://api.example.com/orders", "context": "RETAILER" },
1681
+ { "name": "WEBHOOK_FULFILMENT_URL", "value": "https://api.example.com/fulfilments", "context": "RETAILER" },
1682
+ { "name": "FEATURE_FLAG_RETURNS", "value": "true", "context": "RETAILER" }
1683
+ ]
1684
+ }
1685
+ ```
1686
+
1687
+ Response:
1688
+
1689
+ ```json
1690
+ {
1691
+ "ok": true,
1692
+ "created": 2,
1693
+ "updated": 1,
1694
+ "failed": 0,
1695
+ "total": 3,
1696
+ "results": [
1697
+ { "name": "WEBHOOK_ORDER_URL", "status": "created" },
1698
+ { "name": "WEBHOOK_FULFILMENT_URL", "status": "created" },
1699
+ { "name": "FEATURE_FLAG_RETURNS", "status": "updated" }
1700
+ ]
1701
+ }
1702
+ ```
1703
+
1704
+ ---
1705
+
1706
+ ## `environment.discover`
1707
+
1708
+ Full environment snapshot in one call.
1709
+
1710
+ Returns everything an agent needs to understand the Fluent environment: retailer details, locations, networks, catalogues, workflows, settings, modules, and user context.
1711
+
1712
+ - **Optional**:
1713
+ - `include`: array of sections to include. Default: `["retailer", "locations", "networks", "catalogues"]`. Available: `retailer`, `locations`, `networks`, `catalogues`, `workflows`, `settings`, `modules`, `users`
1714
+
1715
+ **Limitations:**
1716
+
1717
+ - Locations and settings return the first 100 items only (no auto-pagination)
1718
+ - Workflows section uses a transition API probe — may miss workflows with no user actions at the initial state. Use `fluent workflow list` via CLI for definitive listing.
1719
+
1720
+ **Example** — discover retailer, locations, and settings:
1721
+
1722
+ ```json
1723
+ {
1724
+ "include": ["retailer", "locations", "settings"]
1725
+ }
1726
+ ```
1727
+
1728
+ Response:
1729
+
1730
+ ```json
1731
+ {
1732
+ "ok": true,
1733
+ "retailer": { "id": "5", "ref": "HM_TEST", "tradingName": "HM Test", "status": "ACTIVE" },
1734
+ "locations": [
1735
+ { "id": "10", "ref": "LOC_WH_01", "type": "WAREHOUSE", "status": "ACTIVE", "name": "Main Warehouse" }
1736
+ ],
1737
+ "settings": [
1738
+ { "name": "WEBHOOK_ORDER_URL", "value": "https://...", "context": "RETAILER", "contextId": 5 }
1739
+ ]
1740
+ }
1741
+ ```
1742
+
1743
+ ---
1744
+
1745
+ ## `environment.validate`
1746
+
1747
+ Pre-flight environment validation. Runs configurable checks before E2E tests or deployments.
1748
+
1749
+ - **Optional**:
1750
+ - `checks`: array of checks to run. Default: `["auth", "retailer", "locations"]`. Available: `auth`, `retailer`, `locations`, `inventory`, `workflows`, `settings`, `modules`
1751
+
1752
+ **Available checks:**
1753
+
1754
+ | Check | What it validates |
1755
+ |---|---|
1756
+ | `auth` | Token valid, permissions sufficient |
1757
+ | `retailer` | Retailer exists and is active |
1758
+ | `locations` | At least one warehouse location exists |
1759
+ | `inventory` | At least one product with stock |
1760
+ | `workflows` | Key workflows deployed (ORDER, FULFILMENT) |
1761
+ | `settings` | Critical settings exist |
1762
+ | `modules` | Expected modules deployed |
1763
+
1764
+ Returns pass/fail per check with severity and actionable messages.
1765
+
1766
+ **Example** — full pre-flight validation:
1767
+
1768
+ ```json
1769
+ {
1770
+ "checks": ["auth", "retailer", "locations", "workflows", "settings"]
1771
+ }
1772
+ ```
1773
+
1774
+ Response:
1775
+
1776
+ ```json
1777
+ {
1778
+ "ok": true,
1779
+ "results": [
1780
+ { "check": "auth", "passed": true, "severity": "critical", "message": "Authenticated as admin@example.com" },
1781
+ { "check": "retailer", "passed": true, "severity": "critical", "message": "Retailer HM_TEST (ID 5) is ACTIVE" },
1782
+ { "check": "locations", "passed": true, "severity": "high", "message": "Found 3 locations (2 warehouses)" },
1783
+ { "check": "workflows", "passed": false, "severity": "high", "message": "Missing workflow: FULFILMENT_OPTIONS::HD" },
1784
+ { "check": "settings", "passed": true, "severity": "medium", "message": "12 retailer settings found" }
1785
+ ],
1786
+ "summary": { "passed": 4, "failed": 1, "total": 5 }
1787
+ }
1788
+ ```
1789
+
1790
+ ---
1791
+
1792
+ ## `test.assert`
1793
+
1794
+ Assert entity state matches expectations with optional polling.
1795
+
1796
+ Deep assertion on entity fields, attributes, and edge counts/statuses with human-readable failure messages.
1797
+
1798
+ - **Required**:
1799
+ - `entityType`: entity type (ORDER, FULFILMENT, etc.)
1800
+ - `assertions`: object with expected values
1801
+ - **Optional**:
1802
+ - `id`: entity ID (preferred)
1803
+ - `ref`: entity ref (fallback)
1804
+ - `poll`: if `true`, retry until assertions pass or timeout (default `false`)
1805
+ - `timeoutMs`: polling timeout in ms, 1000–300000 (default `60000`)
1806
+ - `intervalMs`: polling interval in ms, 1000–30000 (default `5000`)
1807
+
1808
+ **Assertion types:**
1809
+
1810
+ | Assertion | Description |
1811
+ |---|---|
1812
+ | `status` | Exact match on entity status |
1813
+ | `type` | Exact match on entity type |
1814
+ | `subtype` | Exact match on entity subtype |
1815
+ | `attributes` | Key-value pairs that must be present on the entity |
1816
+ | `edges` | Min/max count and status on related entities (e.g., fulfilments) |
1817
+
1818
+ **Polling mode:** Set `poll: true` to retry assertions on an interval until they pass or the timeout is reached. Useful after sending events when state changes are asynchronous.
1819
+
1820
+ **Example** — assert order is BOOKED with at least 1 fulfilment (polling):
1821
+
1822
+ ```json
1823
+ {
1824
+ "entityType": "ORDER",
1825
+ "ref": "HD-001",
1826
+ "assertions": {
1827
+ "status": "BOOKED",
1828
+ "edges": {
1829
+ "fulfilments": { "minCount": 1 }
1830
+ }
1831
+ },
1832
+ "poll": true,
1833
+ "timeoutMs": 60000,
1834
+ "intervalMs": 5000
1835
+ }
1836
+ ```
1837
+
1838
+ Response (pass):
1839
+
1840
+ ```json
1841
+ {
1842
+ "ok": true,
1843
+ "passed": true,
1844
+ "entityType": "ORDER",
1845
+ "entity": { "id": "12345", "ref": "HD-001", "status": "BOOKED" },
1846
+ "assertions": {
1847
+ "status": { "expected": "BOOKED", "actual": "BOOKED", "passed": true },
1848
+ "edges.fulfilments.minCount": { "expected": 1, "actual": 2, "passed": true }
1849
+ },
1850
+ "polling": { "attempts": 3, "elapsed": 12500 }
1851
+ }
1852
+ ```
1853
+
1854
+ Response (fail after timeout):
1855
+
1856
+ ```json
1857
+ {
1858
+ "ok": true,
1859
+ "passed": false,
1860
+ "entityType": "ORDER",
1861
+ "entity": { "id": "12345", "ref": "HD-001", "status": "CREATED" },
1862
+ "assertions": {
1863
+ "status": { "expected": "BOOKED", "actual": "CREATED", "passed": false }
1864
+ },
1865
+ "polling": { "attempts": 12, "elapsed": 60000, "timedOut": true },
1866
+ "failureMessage": "Assertion failed: status expected \"BOOKED\" but got \"CREATED\" (timed out after 60000ms)"
1867
+ }
1868
+ ```
1869
+
1870
+ ---
1871
+
1872
+ ## Retry and idempotency matrix
1873
+
1874
+ | Category | Tools | Retry behavior |
1875
+ |------|----------|-------------|
1876
+ | Read operations | `event.get`, `event.list`, `event.flowInspect`, `metrics.query`, `metrics.healthCheck`, `metrics.sloReport`, `metrics.labelCatalog`, `metrics.topEvents`, `workflow.transitions`, `workflow.get`, `workflow.list`, `graphql.query` (query), `graphql.queryAll`, `batch.status`, `batch.batchStatus`, `batch.results`, `graphql.introspect`, `connection.test`, `entity.get`, `setting.get`, `environment.discover`, `environment.validate`, `test.assert`, `plugin.list` | retry + backoff |
1877
+ | Write operations | `event.send`, `batch.create`, `batch.send`, `graphql.query` (mutation), `graphql.batchMutate`, `entity.create`, `entity.update`, `workflow.upload`, `setting.upsert`, `setting.bulkUpsert` | timeout-only (no automatic retry) |
1878
+ | Local validation / no API | `config.validate`, `health.ping`, `event.build`, `webhook.validate`, `workflow.diff`, `workflow.simulate` | not applicable |
1879
+
1880
+ ## Tool inventory summary (39 tools)
1881
+
1882
+ | Tool | Category | Requires SDK | Retry | Description |
1883
+ |------|----------|:------------:|:-----:|-------------|
1884
+ | `config.validate` | Diagnostics | No | — | Validate auth/base URL configuration |
1885
+ | `health.ping` | Diagnostics | No | — | Quick health check with config summary |
1886
+ | `connection.test` | Diagnostics | Yes | Yes | Full auth + GraphQL end-to-end test (returns user/account context) |
1887
+ | `event.build` | Events | No | — | Build event payload (no API call) |
1888
+ | `event.send` | Events | Yes | No | Send event (async/sync, supports dryRun) |
1889
+ | `event.get` | Events | Yes | Yes | Get single event by ID |
1890
+ | `event.list` | Events | Yes | Yes | List/filter events with pagination and optional analysis mode |
1891
+ | `event.flowInspect` | Events | Yes | Yes | One-call root-entity flow forensics with compact/full modes |
1892
+ | `metrics.query` | Metrics | Yes | Yes | Query Prometheus metrics (instant/range) |
1893
+ | `metrics.healthCheck` | Metrics | Yes | Yes | One-call anomaly assessment with Prometheus + Event API fallback |
1894
+ | `metrics.sloReport` | Metrics | Yes | Yes | SLO snapshot with rates, latency, and threshold findings |
1895
+ | `metrics.labelCatalog` | Metrics | Yes | Yes | Label discovery for a metric (live sampling + known hints) |
1896
+ | `metrics.topEvents` | Metrics | Yes | Yes | Ranked event analytics using Event API aggregation |
1897
+ | `workflow.transitions` | Orchestration | Yes | Yes | Query available user actions/transitions at any entity state |
1898
+ | `plugin.list` | Orchestration | Yes | Yes | List registered rules with optional name filter |
1899
+ | `graphql.query` | GraphQL | Yes | Varies | Execute single-page query or mutation (retries for queries only) |
1900
+ | `graphql.queryAll` | GraphQL | Yes | Yes | Auto-paginated query — follows cursors across all pages |
1901
+ | `graphql.batchMutate` | GraphQL | Yes | No | Execute up to 50 aliased mutations in one request |
1902
+ | `graphql.introspect` | GraphQL | Yes | Yes | Schema introspection (types, mutations, queries) |
1903
+ | `batch.create` | Batch | Yes | No | Create ingestion job |
1904
+ | `batch.send` | Batch | Yes | No | Send records to job |
1905
+ | `batch.status` | Batch | Yes | Yes | Check job status |
1906
+ | `batch.batchStatus` | Batch | Yes | Yes | Check specific batch within a job |
1907
+ | `batch.results` | Batch | Yes | Yes | Get per-record job outcomes |
1908
+ | `webhook.validate` | Webhooks | No | — | Validate webhook payload + optional signature verification |
1909
+ | `entity.create` | Entity | Yes | No | Type-safe entity creation with field validation and gotcha knowledge (12 entity types) |
1910
+ | `entity.update` | Entity | Yes | No | Status-aware entity updates with optional transition validation |
1911
+ | `entity.get` | Entity | Yes | Yes | Unified entity lookup by ID or ref with optional edge inclusion |
1912
+ | `workflow.get` | Workflow Mgmt | Yes | Yes | Fetch specific workflow by entityType + entitySubtype via REST API |
1913
+ | `workflow.list` | Workflow Mgmt | Yes | Yes | List all workflows for a retailer (deduplicated to latest versions) |
1914
+ | `workflow.upload` | Workflow Mgmt | Yes | No | Deploy workflow JSON via REST API with structure validation |
1915
+ | `workflow.diff` | Workflow Mgmt | No | — | Compare two workflows — rulesets, statuses, risk assessment, mermaid output |
1916
+ | `workflow.simulate` | Workflow Mgmt | No | — | Static prediction of event outcomes from workflow JSON |
1917
+ | `setting.get` | Settings | Yes | Yes | Fetch settings by name (wildcards supported), optionally save to local file |
1918
+ | `setting.upsert` | Settings | Yes | No | Create or update a setting with upsert semantics |
1919
+ | `setting.bulkUpsert` | Settings | Yes | No | Batch create/update up to 50 settings with per-setting error handling |
1920
+ | `environment.discover` | Environment | Yes | Yes | Full environment snapshot — retailer, locations, networks, catalogues, settings, modules |
1921
+ | `environment.validate` | Environment | Yes | Yes | Pre-flight validation checks: auth, retailer, locations, inventory, workflows |
1922
+ | `test.assert` | Test | Yes | Yes | Assert entity state with status, attribute, and edge assertions + polling mode |
1923
+