# fp

> Lodash-style value transforms (fp::get/pick/take/…) and fp::pipe — worker-side pipelines that move big values function→function without routing them through the model.

| field | value |
|-------|-------|
| version | 0.2.2 |
| type | binary |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, i686-pc-windows-msvc, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii worker add fp@0.2.2
```

## readme

# fp

Lodash-style value transforms and `fp::pipe` — worker-side pipelines that
move big values function→function over the iii bus without routing them
through the model.

## Install

    iii worker add fp

The worker is a `deploy: binary` Rust worker with no configuration.

## Why

An agent that fetches a document and re-types it into the next call's
arguments burns its context window and stalls the provider stream mid-call.
`fp::pipe` runs the whole move in one call — each step triggers a function
and its result lands in the next step's payload — so the value flows
worker→worker and the chat only ever sees per-step sizes and a preview.

While the worker is connected it also injects a usage section into the agent
system prompt via the harness `pre-generate` hook (`fp::inject-guidance`),
so the guidance is presence-gated: no fp worker, no prompt text. The
binding is one-shot at startup and relies on the engine's recoverable
triggers (iii #1962): bound before the harness is up, it parks as a pending
intent and activates when the harness registers the trigger type.

## Functions

| function | lodash | request |
|---|---|---|
| `fp::pipe` | — | `{ through: [{function, payload?, into?}], preview_chars? }` |
| `fp::get` | `_.get` | `{ value, path }` — JSON pointer; a miss names the available keys |
| `fp::pick` | `_.pick` | `{ value, paths }` — subset an object by top-level keys |
| `fp::omit` | `_.omit` | `{ value, paths }` — drop top-level keys |
| `fp::take` | `_.take` | `{ value, n }` — first n array elements / string chars |
| `fp::drop` | `_.drop` | `{ value, n }` — skip the first n |
| `fp::map` | `_.map` | `{ value, path }` — pluck a pointer from each element; misses → null |
| `fp::filter` | `_.filter` | `{ value, matches }` — keep elements matching a partial object |
| `fp::split` | `_.split` | `{ value, separator }` |
| `fp::join` | `_.join` | `{ value, separator? }` (default `,`) |
| `fp::uniq` | `_.uniq` | `{ value }` — dedupe, first occurrences win |
| `fp::size` | `_.size` | `{ value }` — array length / string chars / object key count |
| `fp::compact` | `_.compact` | `{ value }` — remove `null` elements (`0`/`false`/`""` are kept) |
| `fp::nth` | `_.nth` | `{ value, n }` — element at index; negative counts from the end |
| `fp::getOr` | `fp.getOr` | `{ value, path, default }` — pointer value, or `default` on a miss |
| `fp::flatten` | `_.flatten` | `{ value }` — unnest one level |
| `fp::sortBy` | `_.sortBy` | `{ value, path }` — stable ascending sort by a plucked pointer (`""` = the element itself) |
| `fp::reverse` | `_.reverse` | `{ value }` — reversed copy (immutable, fp-style) |

Transforms take their input at `value` and return `{ value }`. They deviate
from lodash where silence would thread garbage through a pipe: a type
mismatch is an error (not a silent `{}`); a `map` path matching NO element
errors naming what was available (pointers pluck stored fields — no computed
properties like `/length`; `fp::size` counts); `compact` removes only `null`
(lodash's full-falsey removal would eat legitimate `0`/`false`/`""`);
`nth` out of bounds and `size` on a non-collection error instead of returning
`undefined`/`0`; `sortBy` requires every element to carry the key with one
comparable type. Sporadic `map` misses still become `null`, like lodash —
`fp::compact` drops them.

## The pipe

```jsonc
fp::pipe { through: [
  { function: "scrapling::fetch",
    payload:  { url: "https://…", format: "markdown", main_content_only: true } },
  { function: "fp::get",  payload: { path: "/content" } },
  { function: "fp::take", payload: { n: 20000 } },
  { function: "state::set",   payload: { scope: "research", key: "article" } }
]}
```

- Step N's result lands in step N+1's payload at `into` (a JSON pointer,
  default `/value` — which is exactly where the transforms and `state::set`
  read their input, so most pipes need no `into` at all).
- A transform step threads the transformed value itself — the `{ value }`
  wrapper in the function table is only its direct-call response shape, so
  there is never a `{value}` layer to unwrap between steps.
- The FIRST step receives no threaded value: start with a producing function
  (a fetch, a `state::get`) or seed a leading transform via `payload.value`.
  An unseeded leading transform fails validation before anything runs.
- `fp::*` transform steps run inline in this worker; every other step is
  one bus trigger with a 120 s budget. The whole pipe must also fit the
  caller's own dispatch timeout on `fp::pipe`.
- 1–12 steps. The response is receipts only: `{ steps: [{function, chars}],
  value_preview }` (`preview_chars` sizes the preview, default 400, capped at
  8000 — the receipt must never become the bulk channel it replaces).
- A failing step stops the pipe; the error carries the completed-step trail.

### Boundaries

- Steps run with THIS worker's authority, not the calling agent's per-step
  dispatch policy — the `fp::pipe` call itself is the policy/approval
  surface (an approver sees the full step list). For that reason the pipe is
  not agent-callable without approval by default; the pure transforms are.
  See `iii-permissions.yaml`.
- Statically refused as steps: `shell::*`/`coder::*` (need the harness
  fs_scope stamp), `engine::register_trigger`/`engine::unregister_trigger`
  (need the harness trusted-session stamp), nested pipes, and every class the
  agent policy hard-denies — `session::*`/`approval::*`,
  `configuration::*`/`oauth::*` (credentials), `router::*`/`provider::*`
  (model spend), `harness::*`/`run::*` (turn control), `stream::*` and bus
  internals, `*::on-config-change` — because steps run with worker authority
  and must not ride past those denies. `state::*` is the deliberate
  exception: persisting the threaded value is the pipe's purpose, and the
  pipe call itself is the approval surface.

## api reference

```json
{
  "functions": [
    {
      "description": "Remove null elements from an array (lodash _.compact, null-only: 0, false and \"\" are KEPT so plucked data survives): { value }. Pairs with fp::map, whose sporadic misses become null. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::compact",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Skip the first n elements of an array or the first n characters of a string (lodash _.drop): { value, n }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::drop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "n": {
            "description": "How many array elements / string characters to skip.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "n",
          "value"
        ],
        "title": "DropRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Keep array elements whose properties equal a partial object (lodash _.filter with a matches iteratee): { value, matches: { status: \"active\" } }. matches is a partial OBJECT (never a bare string/number) and only object elements can match. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::filter",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "matches": {
            "additionalProperties": true,
            "description": "Partial object an element's top-level properties must equal.",
            "type": "object"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "matches",
          "value"
        ],
        "title": "FilterRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Flatten an array one level (lodash _.flatten): [1,[2],[[3]]] -> [1,2,[3]]. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::flatten",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Extract the single value at a JSON pointer (lodash _.get): { value, path: \"/content\" }. A miss names the keys that were available. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "JSON pointer selecting the value to return (e.g. `/content`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "GetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Extract the value at a JSON pointer, or `default` when the path misses (lodash fp.getOr): { value, path, default }. A stored null is a hit, not a miss. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::getOr",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "default": {
            "description": "Returned when the path matches nothing (a stored null is a hit)."
          },
          "path": {
            "description": "JSON pointer selecting the value to return (e.g. `/content`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "default",
          "path",
          "value"
        ],
        "title": "GetOrRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal pre_generate hook: appends fp::pipe usage guidance to the agent system prompt. Bound to harness::hook::pre-generate at worker startup; not called directly.",
      "metadata": {
        "internal": true
      },
      "name": "fp::inject-guidance",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "GenerateContext": {
            "properties": {
              "system_prompt": {
                "default": "",
                "description": "The system prompt assembled so far (base + any prior hook's mutation).",
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "description": "The slice of the `pre_generate` hook envelope we read (lenient: ignores every other field the harness sends). The harness nests the live generation context under `generate` (see harness `HookRunner::run_pre_generate`).",
        "properties": {
          "generate": {
            "$ref": "#/definitions/GenerateContext"
          }
        },
        "title": "PreGenerateEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PreGenerateMutations": {
            "description": "The harness applies `system_prompt` only when the key is present (`HookRunner::run_pre_generate` — `if m.system_prompt.is_some()`), so `None` serializes to an empty object: the safe no-op that preserves the harness's assembled prompt.",
            "properties": {
              "system_prompt": {
                "description": "Full replacement system prompt (base + appended guidance). The harness overwrites, it does not merge.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "description": "Hook envelope returned to the harness: the mutations to apply to the generation.",
        "properties": {
          "mutations": {
            "$ref": "#/definitions/PreGenerateMutations"
          }
        },
        "required": [
          "mutations"
        ],
        "title": "PreGenerateResponse",
        "type": "object"
      }
    },
    {
      "description": "Join array elements into one string (lodash _.join): { value, separator } (separator defaults to \",\"). Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::join",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "separator": {
            "default": ",",
            "description": "Separator between elements (default `\",\"`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "JoinRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Pluck the value at a JSON pointer from each array element (lodash _.map with a property iteratee): { value, path: \"/id\" }. Sporadic misses become null (fp::compact drops them), but a path matching NO element errors — pointers pluck stored fields, not computed properties like /length (fp::size counts). Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::map",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "JSON pointer plucked from each element (e.g. `/id`). Sporadic misses become null; a path matching NO element is an error (stored fields only — no computed properties like `/length`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "MapRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Element at index n; negative n counts from the end (lodash _.nth): { value, n: -1 } is the last element. Out of bounds errors naming the length. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::nth",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "n": {
            "description": "Index to pluck; negative counts from the end (`-1` = last element).",
            "format": "int64",
            "type": "integer"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "n",
          "value"
        ],
        "title": "NthRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Drop top-level keys from an object (lodash _.omit): { value, paths: [\"a\",\"b\"] }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::omit",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "description": "Top-level keys to drop; missing keys are ignored.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "paths",
          "value"
        ],
        "title": "OmitRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Subset an object by top-level keys (lodash _.pick): { value, paths: [\"a\",\"b\"] } -> {a, b}; missing keys are omitted. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::pick",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "description": "Top-level keys to keep; missing keys are silently omitted.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "paths",
          "value"
        ],
        "title": "PickRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Run a short worker-side pipeline in one call: each step triggers a function and its result lands in the next step's payload at `into` (default \"/value\") — large values flow worker→worker and never enter the chat. fp::* transform steps (get/pick/omit/take/drop/map/filter/split/join/uniq/size/compact/nth/getOr/flatten/sortBy/reverse/when, lodash semantics) run inline and thread the transformed value itself — the {value} wrapper they return when called directly never appears between steps. A failing fp::when guard STOPS the pipe (`short_circuited: true`), so a trailing write step runs only when its condition holds. The FIRST step receives no threaded value: start with a producing function (a fetch, a state::get) or seed a leading transform via payload.value. Example: fetch an article and persist it trimmed: through=[{function:'scrapling::fetch', payload:{url,format:'markdown'}}, {function:'fp::get', payload:{path:'/content'}}, {function:'fp::take', payload:{n:20000}}, {function:'state::set', payload:{scope,key}}]. Returns per-step sizes and a preview, not the value.",
      "metadata": {},
      "name": "fp::pipe",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PipeStep": {
            "additionalProperties": false,
            "properties": {
              "function": {
                "description": "Function id to trigger (e.g. `scrapling::fetch`, `fp::get`, `state::set`).",
                "type": "string"
              },
              "into": {
                "default": null,
                "description": "JSON pointer in THIS step's payload where the previous step's value lands (default `/value`). Object keys only (`~0`/`~1` escapes are decoded); indexing into arrays is unsupported.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "payload": {
                "default": null,
                "description": "Base payload for the call."
              }
            },
            "required": [
              "function"
            ],
            "type": "object"
          }
        },
        "properties": {
          "preview_chars": {
            "default": null,
            "description": "Preview length of the final threaded value in the receipt (capped at 8000).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "through": {
            "description": "Steps run in order; step N+1 receives step N's threaded value.",
            "items": {
              "$ref": "#/definitions/PipeStep"
            },
            "type": "array"
          }
        },
        "required": [
          "through"
        ],
        "title": "PipeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "StepReceipt": {
            "properties": {
              "chars": {
                "description": "Size of the value threaded OUT of this step (chars of its string or serialized form).",
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "function": {
                "type": "string"
              },
              "note": {
                "description": "Set when the threaded value REPLACED a non-null literal already in this step's payload at the `into` pointer — almost always an omitted `into` on a write step clobbering the value it meant to write.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "chars",
              "function"
            ],
            "type": "object"
          }
        },
        "properties": {
          "short_circuited": {
            "description": "True when a `fp::when` guard failed and stopped the pipe: the receipts end at the guard and NO later step ran. Absent (false) on a full run.",
            "type": "boolean"
          },
          "steps": {
            "items": {
              "$ref": "#/definitions/StepReceipt"
            },
            "type": "array"
          },
          "value_preview": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "short_circuited",
          "steps"
        ],
        "title": "PipeResponse",
        "type": "object"
      }
    },
    {
      "description": "Reverse an array (lodash _.reverse, immutable like lodash/fp): { value }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::reverse",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Count a collection (lodash _.size): array length, string chars, or object key count: { value }. Non-collections error instead of returning 0. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::size",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Sort array elements ascending by the value at a JSON pointer (lodash _.sortBy, stable): { value, path } — path \"\" sorts the elements themselves (primitives). Keys must all be numbers, strings, or booleans; an element missing the path errors. Pair with fp::reverse for descending. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::sortBy",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "JSON pointer plucked from each element as the sort key (e.g. `/score`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "path",
          "value"
        ],
        "title": "SortByRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Split a string into an array of strings (lodash _.split): { value, separator: \"\\n\" }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::split",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "separator": {
            "description": "Non-empty separator to split on (e.g. `\"\\n\"`).",
            "type": "string"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "separator",
          "value"
        ],
        "title": "SplitRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Keep the first n elements of an array or the first n characters of a string (lodash _.take): { value, n }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::take",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "n": {
            "description": "How many array elements / string characters to keep.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "n",
          "value"
        ],
        "title": "TakeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Deduplicate an array, keeping first occurrences (lodash _.uniq): { value }. Mainly useful as a fp::pipe step.",
      "metadata": {},
      "name": "fp::uniq",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Shared request for the value-only ops (uniq/size/compact/flatten/reverse).",
        "properties": {
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "value"
        ],
        "title": "ValueRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "value": true
        },
        "required": [
          "value"
        ],
        "title": "UtilResponse",
        "type": "object"
      }
    },
    {
      "description": "Guard: test the value at a JSON pointer ({ value, path?, op, to? }; ops ==, !=, >, >=, <, <=, exists, not_empty; path defaults to the whole value; a pointer miss FAILS the guard, it never errors). Direct calls return { passed, value }. As a fp::pipe step, a passing guard threads the ORIGINAL value onward unchanged and a failing one STOPS the pipe (`short_circuited: true`), so a trailing write step (e.g. state::set) runs only when the condition holds.",
      "metadata": {},
      "name": "fp::when",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WhenOp": {
            "description": "The comparison a `fp::when` guard runs.",
            "enum": [
              "==",
              "!=",
              ">",
              ">=",
              "<",
              "<=",
              "exists",
              "not_empty"
            ],
            "type": "string"
          }
        },
        "properties": {
          "op": {
            "allOf": [
              {
                "$ref": "#/definitions/WhenOp"
              }
            ],
            "description": "One of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"exists\", \"not_empty\"."
          },
          "path": {
            "default": null,
            "description": "JSON pointer selecting what to test (default: the whole value).",
            "type": [
              "string",
              "null"
            ]
          },
          "to": {
            "default": null,
            "description": "Right-hand side for the comparison ops; meaningless (and rejected) for exists / not_empty."
          },
          "value": {
            "description": "Input value (a pipe lands the previous step's value here)."
          }
        },
        "required": [
          "op",
          "value"
        ],
        "title": "WhenRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "passed": {
            "type": "boolean"
          },
          "value": {
            "description": "The ORIGINAL input value, untouched — guards test, they never reshape."
          }
        },
        "required": [
          "passed",
          "value"
        ],
        "title": "WhenResponse",
        "type": "object"
      }
    }
  ],
  "triggers": []
}
```
