@demlik/tea 0.20.0 → 0.22.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.
- package/README.md +11 -2
- package/dist/agent/index.d.ts +36 -23
- package/dist/agent/index.js +12 -10
- package/dist/{chunk-UHAQ4PI6.js → chunk-2A5XTKQQ.js} +205 -59
- package/dist/chunk-2A5XTKQQ.js.map +1 -0
- package/dist/{chunk-JAKM53JF.js → chunk-7K7SEYCH.js} +16 -15
- package/dist/chunk-7K7SEYCH.js.map +1 -0
- package/dist/{chunk-RKVUVALV.js → chunk-A3QPMX5M.js} +31 -9
- package/dist/chunk-A3QPMX5M.js.map +1 -0
- package/dist/{chunk-DVNXEB7V.js → chunk-BVFS3ICC.js} +3 -3
- package/dist/chunk-BVFS3ICC.js.map +1 -0
- package/dist/{chunk-FTCYY2DO.js → chunk-CPE5ZQXQ.js} +3 -3
- package/dist/{chunk-FTCYY2DO.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
- package/dist/chunk-DEAPNMKO.js +11 -0
- package/dist/chunk-DEAPNMKO.js.map +1 -0
- package/dist/{chunk-XWIE5V7R.js → chunk-ETRNSJCR.js} +4 -4
- package/dist/{chunk-XWIE5V7R.js.map → chunk-ETRNSJCR.js.map} +1 -1
- package/dist/chunk-FXWDDNGD.js +98 -0
- package/dist/chunk-FXWDDNGD.js.map +1 -0
- package/dist/chunk-GFGOQIEC.js +10 -0
- package/dist/chunk-GFGOQIEC.js.map +1 -0
- package/dist/{chunk-2R62Q5UK.js → chunk-IAHEYPYJ.js} +3 -3
- package/dist/{chunk-2R62Q5UK.js.map → chunk-IAHEYPYJ.js.map} +1 -1
- package/dist/{chunk-NGYFBD5G.js → chunk-J5JNLXO7.js} +3 -3
- package/dist/{chunk-NGYFBD5G.js.map → chunk-J5JNLXO7.js.map} +1 -1
- package/dist/{chunk-72CN3VPY.js → chunk-NFOGYDRS.js} +5 -5
- package/dist/{chunk-72CN3VPY.js.map → chunk-NFOGYDRS.js.map} +1 -1
- package/dist/{chunk-A73IJ27X.js → chunk-R2YNDRGL.js} +3 -3
- package/dist/{chunk-A73IJ27X.js.map → chunk-R2YNDRGL.js.map} +1 -1
- package/dist/{chunk-5HVKDHY6.js → chunk-R5NXOPL2.js} +3 -3
- package/dist/{chunk-5HVKDHY6.js.map → chunk-R5NXOPL2.js.map} +1 -1
- package/dist/chunk-RMBZ7YH7.js +52 -0
- package/dist/chunk-RMBZ7YH7.js.map +1 -0
- package/dist/{chunk-AIXC33SE.js → chunk-SHXUSTHM.js} +62 -15
- package/dist/chunk-SHXUSTHM.js.map +1 -0
- package/dist/{chunk-TAKPC6SD.js → chunk-UGTC33VH.js} +4 -4
- package/dist/{chunk-TAKPC6SD.js.map → chunk-UGTC33VH.js.map} +1 -1
- package/dist/{chunk-EA47MI5Q.js → chunk-VH5MYJ2K.js} +4 -4
- package/dist/{chunk-EA47MI5Q.js.map → chunk-VH5MYJ2K.js.map} +1 -1
- package/dist/{chunk-2774I242.js → chunk-WZEDIUWR.js} +21 -20
- package/dist/chunk-WZEDIUWR.js.map +1 -0
- package/dist/{chunk-TGJLXZAU.js → chunk-ZRRET3RP.js} +11 -11
- package/dist/chunk-ZRRET3RP.js.map +1 -0
- package/dist/{core-CtvAyPpH.d.ts → core-DNDAEhO2.d.ts} +9 -12
- package/dist/devtools/index.d.ts +4 -4
- package/dist/do/index.d.ts +103 -38
- package/dist/do/index.js +40 -21
- package/dist/do/index.js.map +1 -1
- package/dist/{drive-loop-Dw1sGSsW.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
- package/dist/{durable-effects-Ump5lSdO.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
- package/dist/effect/index.d.ts +121 -28
- package/dist/effect/index.js +45 -31
- package/dist/effect/index.js.map +1 -1
- package/dist/extension/index.d.ts +3 -3
- package/dist/extension/index.js +2 -2
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +17 -13
- package/dist/flow/index.js +15 -12
- package/dist/flow/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +3 -3
- package/dist/idempotency/index.js +3 -3
- package/dist/{index-CrcoVfd_.d.ts → index-9P1PaXIV.d.ts} +2 -2
- package/dist/{index-D3NY3VCN.d.ts → index-BBLI2WWN.d.ts} +1 -1
- package/dist/{index-Dl71V7kt.d.ts → index-C1WhBZ6a.d.ts} +4 -5
- package/dist/{index-CKtEKGyp.d.ts → index-C92SgNWz.d.ts} +3 -3
- package/dist/{index-CZZ3HI-Z.d.ts → index-CAsUE_d1.d.ts} +4 -4
- package/dist/{index-EIgfl83J.d.ts → index-CO34esWT.d.ts} +2 -2
- package/dist/{index-Bk2hBUdK.d.ts → index-COeX9Bmz.d.ts} +11 -9
- package/dist/{index-CVU7SK3a.d.ts → index-Cg0QXnzf.d.ts} +2 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +2 -2
- package/dist/jev/index.d.ts +15 -15
- package/dist/jev/index.js +5 -5
- package/dist/jev/index.js.map +1 -1
- package/dist/laws-DTtCDDlU.d.ts +135 -0
- package/dist/loop-DReKGV1X.d.ts +70 -0
- package/dist/{machine-Dtre2TmN.d.ts → machine-C0WhOAyB.d.ts} +7 -7
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +4 -3
- package/dist/mem/index.js +2 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +57 -9
- package/dist/node/index.js +175 -68
- package/dist/node/index.js.map +1 -1
- package/dist/otel/index.d.ts +28 -12
- package/dist/otel/index.js +25 -21
- package/dist/otel/index.js.map +1 -1
- package/dist/paginate/index.d.ts +4 -4
- package/dist/paginate/index.js +5 -5
- package/dist/parity/index.d.ts +11 -15
- package/dist/parity/index.js +4 -9
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/index.d.ts +279 -67
- package/dist/pbt/index.js +299 -58
- package/dist/pbt/index.js.map +1 -1
- package/dist/persistence/index.d.ts +10 -12
- package/dist/persistence/index.js +4 -4
- package/dist/promise/index.d.ts +118 -21
- package/dist/promise/index.js +6 -4
- package/dist/react/index.d.ts +34 -21
- package/dist/react/index.js +271 -42
- package/dist/react/index.js.map +1 -1
- package/dist/resilience/index.d.ts +6 -6
- package/dist/resilience/index.js +5 -5
- package/dist/{run-fiBe7pOG.d.ts → run-Cnw4qXj2.d.ts} +105 -23
- package/dist/{runtime-types-DEluQgGO.d.ts → runtime-types-CP-ykwgf.d.ts} +157 -29
- package/dist/testing/effect/index.d.ts +162 -9
- package/dist/testing/effect/index.js +110 -10
- package/dist/testing/effect/index.js.map +1 -1
- package/dist/testing/index.d.ts +6 -8
- package/dist/testing/index.js +4 -24
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/promise/index.d.ts +4 -4
- package/dist/testing/promise/index.js +5 -5
- package/dist/timing/index.d.ts +4 -4
- package/dist/timing/index.js +4 -4
- package/dist/{types-CXNzlbAg.d.ts → types-S53WDy_h.d.ts} +1 -1
- package/dist/work-queue/index.d.ts +4 -4
- package/dist/work-queue/index.js +2 -2
- package/package.json +19 -17
- package/dist/chunk-2774I242.js.map +0 -1
- package/dist/chunk-AIXC33SE.js.map +0 -1
- package/dist/chunk-DVNXEB7V.js.map +0 -1
- package/dist/chunk-HX3C6LDK.js +0 -34
- package/dist/chunk-HX3C6LDK.js.map +0 -1
- package/dist/chunk-JAKM53JF.js.map +0 -1
- package/dist/chunk-RKVUVALV.js.map +0 -1
- package/dist/chunk-TGJLXZAU.js.map +0 -1
- package/dist/chunk-UHAQ4PI6.js.map +0 -1
package/dist/jev/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/internal/jev/protocol/index.ts","../../src/internal/jev/ask/index.ts","../../src/internal/jev/classify-batch/index.ts"],"names":["at","fail"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * internal/jev/protocol — the wire contract of TypeSafe **Jev** (System One)\n * as types plus two pure functions, and nothing that performs I/O. Internal\n * since #216 — not published on any subpath; the `ask` child that opens the\n * socket imports it from here.\n *\n * The contract is transcribed from `https://docs.typesafe.ai/api`, re-verified\n * 2026-09-21: `POST https://api.typesafe.ai/v1/systemone`, `Authorization:\n * Bearer <API_KEY>`, request `{ state, model, questions }`, response\n * `{ model, answers, usage }`. Where this module and that page disagree, the\n * page is right and this module is a bug.\n *\n * ## What this module is for\n *\n * A Jev call is a map of questions you name, and an answer comes back under\n * each name. That is a shape TypeScript can hold onto, and the whole point of\n * typing it here is that a **choice** question's `criteria` keys ARE its\n * answer's `choice` domain — write the rubric once and the narrowing is free\n * at the call site:\n *\n * ```ts\n * const qs = jevQuestions({\n * category: {\n * type: \"choice\",\n * instructions: \"Which budget line is this?\",\n * criteria: { groceries: \"Supermarkets\", dining: \"Restaurants\" },\n * },\n * });\n * // parsed.answers.category.choice : \"groceries\" | \"dining\"\n * ```\n *\n * `jevQuestions` is an identity function with a `const` type parameter; it\n * exists so the literal keys survive inference without every call site\n * spelling `as const`. `JevAnswers<Q>` is the mapped type that turns such a\n * questions map into its answers map.\n *\n * ## Why parsing is a pure function returning data\n *\n * `parseAnswers` never throws — a response that disagrees with the questions\n * that produced it is a recoverable failure with an obvious next move (retry,\n * re-prompt, give up on this one item), so it is DATA: a closed `JevErr`\n * union of `{ _tag }` values that survives a JSON round-trip into Model\n * (ADR 0011). The type-level guarantee above is a *claim about the wire*, and\n * `parseAnswers` is the one place that claim is checked against a body the\n * network actually produced. Every `unknown` reaches a `JevErr`; nothing\n * reaches a `throw`.\n *\n * The check itself is a walk DERIVED from the questions map, one step per\n * field, and the typed value is the fresh object it builds. Both halves of a\n * choice question's claim are structural in that walk: `choice` must be one of\n * the criteria keys, and `probabilities` must carry exactly those keys — so a\n * distribution missing one or carrying an extra fails the check rather than a\n * separate arm a later reader has to remember. A partial distribution typed `Record<K, number>` is\n * the representable-invalid state this module exists to refuse, and it arrives\n * through this gate or not at all. The envelope is checked by the same schema:\n * `model` is a string and `usage` carries two finite numbers.\n *\n * The walk stops at the first field that fails, and that field's path is\n * mapped onto the `JevErr` arm that names it. It is hand-written rather than a\n * schema library's so the published package depends on none (ADR 0021 §5).\n *\n * ## Why status classification lives here and not in `ask`\n *\n * \"429 and 529 back off, everything else non-2xx is terminal\" is a fact about\n * the protocol, published in the same reference page as the body shapes, and\n * `ask` re-deriving it is how the retry policy and the wire contract drift\n * apart. `classifyStatus` is the single reading.\n *\n * ## Bounds the API enforces and this module does not\n *\n * A Choice takes at most 255 options and a Score takes at most 10 levels.\n * `JevScoreQuestion` encodes the *lower* bound — a Score with fewer than two\n * levels does not compile — because a two-element minimum is a tuple, which\n * costs nothing. The upper bounds are not encoded: spelling them as a union\n * of nine tuple arities (or 255 of them) buys a compile error in place of a\n * `422`, at the price of unreadable diagnostics on every ordinary Score. They\n * are the API's `422`, which `classifyStatus` already calls terminal.\n */\n\n// ── the shared instruction / description slot ──────────────────────────────\n\n/**\n * What every `instructions` and every criterion description accepts.\n *\n * The reference page allows a string, an object, or an array in all three\n * slots: a long question carrying data it refers to is written as an object\n * with the question in one field and the data in the others. Typing it as\n * `string` alone would reject the documented structured form.\n */\nexport type JevText =\n | string\n | Readonly<Record<string, unknown>>\n | readonly unknown[];\n\n// ── questions ──────────────────────────────────────────────────────────────\n\n/** The three question types, as the `type` discriminant spells them. */\nexport type JevQuestionType = \"choice\" | \"score\" | \"noul\";\n\n/**\n * A choice question's rubric: option key → description, or `null` where an\n * option needs no extra detail. The keys are the answer's `choice` domain.\n */\nexport type JevChoiceCriteria = Readonly<Record<string, JevText | null>>;\n\n/** Pick one option from a set you define. */\nexport interface JevChoiceQuestion<\n C extends JevChoiceCriteria = JevChoiceCriteria,\n> {\n readonly type: \"choice\";\n readonly instructions: JevText;\n readonly criteria: C;\n}\n\n/**\n * A score question's rubric: an ORDERED list of level descriptions, at least\n * two of them. The API accepts up to ten.\n */\nexport type JevScoreCriteria = readonly [JevText, JevText, ...JevText[]];\n\n/** Rate the state along an ordered rubric. */\nexport interface JevScoreQuestion {\n readonly type: \"score\";\n readonly instructions: JevText;\n readonly criteria: JevScoreCriteria;\n}\n\n/** A yes/no question. `criteria` is optional; both arms are required when it is present. */\nexport interface JevNoulQuestion {\n readonly type: \"noul\";\n readonly instructions: JevText;\n readonly criteria?: {\n readonly true: JevText;\n readonly false: JevText;\n };\n}\n\n/** One typed question. */\nexport type JevQuestion =\n | JevChoiceQuestion\n | JevScoreQuestion\n | JevNoulQuestion;\n\n/** The `questions` map: an id you choose → the question asked under it. */\nexport type JevQuestionMap = Readonly<Record<string, JevQuestion>>;\n\n/**\n * Identity, with the literal keys kept.\n *\n * The `const` type parameter is the whole function: it preserves each choice\n * question's `criteria` keys as literals, so `JevAnswers<typeof qs>` can name\n * them. Without it the inferred type is `Record<string, string>` and the\n * narrowing this module exists for is gone before `parseAnswers` is reached.\n */\nexport const jevQuestions = <const Q extends JevQuestionMap>(questions: Q): Q =>\n questions;\n\n// ── the request ────────────────────────────────────────────────────────────\n\n/** The evaluation endpoint. */\nexport const JEV_ENDPOINT = \"https://api.typesafe.ai/v1/systemone\" as const;\n\n/** The content to evaluate: text, or structured data. */\nexport type JevState =\n | string\n | Readonly<Record<string, unknown>>\n | readonly unknown[];\n\n/** The request body of `POST /v1/systemone`. */\nexport interface JevRequest<Q extends JevQuestionMap = JevQuestionMap> {\n readonly state: JevState;\n readonly model: string;\n readonly questions: Q;\n}\n\n// ── answers ────────────────────────────────────────────────────────────────\n\n/** The yes/no answer, on a scale from 0 (no) to 1 (yes). */\nexport interface JevNoulAnswer {\n readonly type: \"noul\";\n readonly noul: number;\n}\n\n/**\n * The chosen option and the full distribution over the options.\n *\n * `K` is the choice question's own criteria keys, which is why `choice` is a\n * union rather than a `string` and `probabilities` is total over the options.\n */\nexport interface JevChoiceAnswer<K extends string = string> {\n readonly type: \"choice\";\n readonly choice: K;\n readonly probabilities: Readonly<Record<K, number>>;\n readonly confidence: number;\n}\n\n/**\n * The probability-weighted value across the levels — it can land BETWEEN\n * levels, which is why `score` is a `number` and not an index. `legend` and\n * `probabilities` are keyed by the level index as a string.\n */\nexport interface JevScoreAnswer {\n readonly type: \"score\";\n readonly score: number;\n readonly legend: Readonly<Record<string, string>>;\n readonly probabilities: Readonly<Record<string, number>>;\n readonly confidence: number;\n}\n\n/** One typed answer. */\nexport type JevAnswer = JevChoiceAnswer | JevScoreAnswer | JevNoulAnswer;\n\n/** The answer a given question type yields, with the choice union carried through. */\nexport type JevAnswerFor<Q> = Q extends {\n readonly type: \"choice\";\n readonly criteria: infer C;\n}\n ? JevChoiceAnswer<Extract<keyof C, string>>\n : Q extends { readonly type: \"score\" }\n ? JevScoreAnswer\n : Q extends { readonly type: \"noul\" }\n ? JevNoulAnswer\n : never;\n\n/** A questions map turned into its answers map, id for id. */\nexport type JevAnswers<Q extends JevQuestionMap> = {\n readonly [K in keyof Q]: JevAnswerFor<Q[K]>;\n};\n\n/** Token usage for the request. */\nexport interface JevUsage {\n readonly input_tokens: number;\n readonly output_tokens: number;\n}\n\n/** The response body of `POST /v1/systemone`. */\nexport interface JevResponse<Q extends JevQuestionMap = JevQuestionMap> {\n readonly model: string;\n readonly answers: JevAnswers<Q>;\n readonly usage: JevUsage;\n}\n\n// ── failures are data ──────────────────────────────────────────────────────\n\n/**\n * Every way a response can fail to be the answers to the questions asked.\n *\n * Closed, `{ _tag }`-discriminated, JSON-round-trippable: a consumer folds one\n * of these into Model and routes on `_tag` (ADR 0011). `path` and `id` say\n * WHERE, so a failure names the question it is about rather than the whole\n * call.\n */\nexport type JevErr =\n /** The envelope is not a Jev response at all — not an object, or `model` /\n * `answers` / `usage` missing or of the wrong shape. */\n | { readonly _tag: \"malformed_body\"; readonly reason: string }\n /** A question id in the request has no answer under it in the response. */\n | { readonly _tag: \"missing_answer\"; readonly id: string }\n /** An answer arrived, but its `type` is not its question's. */\n | {\n readonly _tag: \"answer_type_mismatch\";\n readonly id: string;\n readonly expected: JevQuestionType;\n readonly received: string;\n }\n /** A choice answer picked something that is not one of its criteria keys. */\n | {\n readonly _tag: \"off_criteria_choice\";\n readonly id: string;\n readonly choice: string;\n readonly options: readonly string[];\n }\n /** A choice answer's `probabilities` keys are not exactly its question's\n * criteria keys. `JevChoiceAnswer<K>` types that map as TOTAL over `K`, so\n * a partial or off-criteria one is the representable-invalid state this\n * module exists to refuse. `missing` and `extra` say which way it failed. */\n | {\n readonly _tag: \"off_criteria_probabilities\";\n readonly id: string;\n readonly missing: readonly string[];\n readonly extra: readonly string[];\n readonly options: readonly string[];\n }\n /** The answer's `type` is right and its payload is not — a missing\n * `confidence`, a `probabilities` that is not a number map, and so on. */\n | {\n readonly _tag: \"malformed_answer\";\n readonly id: string;\n readonly reason: string;\n };\n\n/**\n * The tags {@link JevErr} is closed over, witnessed against the union.\n *\n * The `satisfies` is the point: it is the compiler checking this record's keys\n * against `JevErr[\"_tag\"]` in BOTH directions — a union arm with no entry here\n * fails totality, and an entry naming no arm fails the excess-property check\n * on the literal. Before it, the set was `ReadonlySet<string>` and a new arm\n * was tied to its runtime guard by reviewer attention alone: omit the entry\n * and {@link isJevErr} silently answers `false` for that arm forever.\n */\nconst JEV_ERR_TAG_WITNESS = {\n malformed_body: true,\n missing_answer: true,\n answer_type_mismatch: true,\n off_criteria_choice: true,\n off_criteria_probabilities: true,\n malformed_answer: true,\n} as const satisfies Record<JevErr[\"_tag\"], true>;\n\n/**\n * The membership set {@link isJevErr} tests against — the single reading of\n * the union's tags, so nothing downstream re-derives it from a `\"_tag\" in x`\n * test.\n */\nconst JEV_ERR_TAGS: ReadonlySet<JevErr[\"_tag\"]> = new Set(\n Object.keys(JEV_ERR_TAG_WITNESS) as readonly JevErr[\"_tag\"][],\n);\n\n/**\n * Is `value` a {@link JevErr}?\n *\n * The discriminant is the `_tag` VALUE against the closed set above, never the\n * bare presence of the key. A `JevAnswers` map is keyed by the caller's own\n * question ids, so a caller may legally name a question `_tag` — and then\n * `\"_tag\" in answers` is `true`. The value under it is an ANSWER, which is\n * always an object and never one of these string literals, so reading the\n * value is the one test a caller's id space cannot reach.\n */\nexport function isJevErr(value: object): value is JevErr {\n if (!(\"_tag\" in value)) return false;\n const tag: unknown = (value as { readonly _tag: unknown })._tag;\n // The set is keyed over the union's tags, and the question being asked is\n // whether an arbitrary string is one of them — a read-only widening, sound\n // because `has` only ever reads.\n const tags: ReadonlySet<string> = JEV_ERR_TAGS;\n return typeof tag === \"string\" && tags.has(tag);\n}\n\n/** What `parseAnswers` returns: typed answers, or one `JevErr`. Never a throw. */\nexport type JevParse<Q extends JevQuestionMap> =\n | {\n readonly ok: true;\n readonly answers: JevAnswers<Q>;\n readonly model: string;\n readonly usage: JevUsage;\n }\n | { readonly ok: false; readonly error: JevErr };\n\n// ── parsing ────────────────────────────────────────────────────────────────\n\ntype Obj = Readonly<Record<string, unknown>>;\n\nconst isObj = (v: unknown): v is Obj =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** The value under `key`, or `undefined` where there is no object to read. */\nconst at = (value: unknown, key: string): unknown =>\n isObj(value) ? value[key] : undefined;\n\nconst err = (\n error: JevErr,\n): { readonly ok: false; readonly error: JevErr } => ({\n ok: false,\n error,\n});\n\n// ── the check the questions derive ─────────────────────────────────\n\n/**\n * Where a body first disagrees with the questions: the path to the offending\n * value, `[]` for the body itself. The check stops at the first one.\n */\ntype Issue = { readonly path: readonly (string | number)[] };\n\n/** A step of the walk: the parsed value, or the first issue under `path`. */\ntype Step<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly issue: Issue };\n\nconst pass = <T>(value: T): Step<T> => ({ ok: true, value });\nconst fail = (path: readonly (string | number)[]): Step<never> => ({\n ok: false,\n issue: { path },\n});\n\n/** A finite number: the wire carries no `NaN` and no infinity. */\nconst isFiniteNumber = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isFinite(v);\n\n/**\n * Check `value` field by field, in the order `fields` lists them, and build a\n * fresh object holding exactly those fields. Keys the list does not name are\n * dropped, never read.\n */\nconst object = (\n value: unknown,\n path: readonly (string | number)[],\n fields: ReadonlyArray<\n readonly [\n string,\n (v: unknown, p: readonly (string | number)[]) => Step<unknown>,\n ]\n >,\n): Step<Obj> => {\n if (!isObj(value)) return fail(path);\n const out: Record<string, unknown> = {};\n for (const [key, check] of fields) {\n const step = check(value[key], [...path, key]);\n if (!step.ok) return step;\n out[key] = step.value;\n }\n return pass(out);\n};\n\n/** A map whose every value passes `isValue`, copied. */\nconst record =\n (isValue: (v: unknown) => boolean) =>\n (value: unknown, path: readonly (string | number)[]): Step<Obj> => {\n if (!isObj(value)) return fail(path);\n for (const [key, v] of Object.entries(value)) {\n if (!isValue(v)) return fail([...path, key]);\n }\n return pass({ ...value });\n };\n\nconst literal =\n (expected: string) =>\n (value: unknown, path: readonly (string | number)[]): Step<string> =>\n value === expected ? pass(expected) : fail(path);\n\nconst finite = (\n value: unknown,\n path: readonly (string | number)[],\n): Step<number> => (isFiniteNumber(value) ? pass(value) : fail(path));\n\nconst string = (\n value: unknown,\n path: readonly (string | number)[],\n): Step<string> => (typeof value === \"string\" ? pass(value) : fail(path));\n\n/**\n * A choice's `probabilities`: TOTAL over the criteria keys and nothing else,\n * which is what `JevChoiceAnswer<K>` types it as. A key the body lacks fails\n * at that key (its value is not a number); a key the criteria do not name\n * fails at the map itself, after every named key has passed.\n */\nconst exactProbabilities =\n (options: readonly string[]) =>\n (value: unknown, path: readonly (string | number)[]): Step<Obj> => {\n const named = object(\n value,\n path,\n options.map((k) => [k, finite] as const),\n );\n if (!named.ok || !isObj(value)) return named;\n return Object.keys(value).every((k) => options.includes(k))\n ? named\n : fail(path);\n };\n\n/**\n * Check one answer against the question that asked for it.\n *\n * Field order is load-bearing — the walk stops at the FIRST failing field and\n * {@link toJevErr} maps that one, so this order is the precedence between two\n * arms that could both fire on one answer.\n *\n * That precedence reads the answer's own subject first: `choice`, the thing\n * the question asked for, then `confidence`, then the `probabilities`\n * distribution over it. A multi-fault body therefore names the semantically\n * interesting arm — an `off_criteria_choice` rather than a bad `confidence` —\n * which is the better report for a caller deciding whether to re-ask the\n * model. Inside `probabilities`, a bad or missing named key is found before an\n * extra one.\n *\n * The precedence is pinned by the \"multi-fault precedence\" cases in\n * `protocol.test.ts`, so a later reorder fails a test rather than this comment.\n */\nconst checkAnswer = (\n question: JevQuestion,\n value: unknown,\n path: readonly (string | number)[],\n): Step<Obj> => {\n switch (question.type) {\n case \"noul\":\n return object(value, path, [\n [\"type\", literal(\"noul\")],\n [\"noul\", finite],\n ]);\n case \"score\":\n return object(value, path, [\n [\"type\", literal(\"score\")],\n [\"confidence\", finite],\n [\"score\", finite],\n [\"legend\", record((v) => typeof v === \"string\")],\n [\"probabilities\", record(isFiniteNumber)],\n ]);\n case \"choice\": {\n const options = Object.keys(question.criteria);\n return object(value, path, [\n [\"type\", literal(\"choice\")],\n [\n \"choice\",\n (v, p) =>\n typeof v === \"string\" && options.includes(v) ? pass(v) : fail(p),\n ],\n [\"confidence\", finite],\n [\"probabilities\", exactProbabilities(options)],\n ]);\n }\n }\n};\n\n/**\n * Check the whole response body: the envelope, then the answers map keyed by\n * the ids the request asked about.\n *\n * Unknown ids are dropped rather than refused — the question-driven reading:\n * the questions are the contract, and a wider response still satisfies it.\n * Dropped means dropped: what comes back is a fresh object, not an alias of\n * the body's own `answers`, so a caller reaching past the type for an id it\n * never asked about no longer finds one.\n */\nconst checkResponse = (\n questions: JevQuestionMap,\n body: unknown,\n): Step<{\n readonly model: string;\n readonly usage: JevUsage;\n readonly answers: Obj;\n}> => {\n const envelope = object(\n body,\n [],\n [\n [\"model\", string],\n [\n \"usage\",\n (v, p) =>\n object(v, p, [\n [\"input_tokens\", finite],\n [\"output_tokens\", finite],\n ]),\n ],\n [\n \"answers\",\n (v, p) =>\n object(\n v,\n p,\n Object.entries(questions).map(\n ([id, question]) =>\n [id, (a, ap) => checkAnswer(question, a, ap)] as const,\n ),\n ),\n ],\n ],\n );\n if (!envelope.ok) return envelope;\n const { model, usage, answers } = envelope.value as {\n readonly model: string;\n readonly usage: JevUsage;\n readonly answers: Obj;\n };\n return pass({ model, usage, answers });\n};\n\n// ── the failure the check produces, as data ────────────────────────\n\n/** What a failed field of an answer says, where the path alone names it. */\nconst ANSWER_FIELD_REASON: Readonly<Record<string, string>> = {\n confidence: \"`confidence` is not a number\",\n score: \"`score` is not a number\",\n legend: \"`legend` is not a map of strings\",\n probabilities: \"`probabilities` is not a map of numbers\",\n choice: \"`choice` is not a string\",\n noul: \"`noul` is not a number\",\n};\n\n/**\n * Map the first issue onto the `JevErr` arm that names it.\n *\n * The issue's path says where, and `body` says what was actually there — which\n * is the difference between \"this answer picked an option nobody offered\" and\n * \"this answer's `choice` is not even a string\", two arms one failed `choice`\n * check covers.\n */\nconst toJevErr = (\n questions: JevQuestionMap,\n body: unknown,\n issue: Issue,\n): JevErr => {\n const [head, id, field] = issue.path;\n\n if (head === \"model\") {\n return { _tag: \"malformed_body\", reason: \"`model` is not a string\" };\n }\n if (head === \"usage\") {\n return {\n _tag: \"malformed_body\",\n reason: \"`usage` is not `{ input_tokens, output_tokens }`\",\n };\n }\n if (head !== \"answers\") {\n return { _tag: \"malformed_body\", reason: \"body is not an object\" };\n }\n\n const answers = at(body, \"answers\");\n if (!isObj(answers) || typeof id !== \"string\") {\n return { _tag: \"malformed_body\", reason: \"`answers` is not an object\" };\n }\n if (!Object.hasOwn(answers, id)) return { _tag: \"missing_answer\", id };\n\n const answer = answers[id];\n const question = questions[id];\n if (question === undefined || field === undefined) {\n return { _tag: \"malformed_answer\", id, reason: \"answer is not an object\" };\n }\n\n if (field === \"type\") {\n const received = at(answer, \"type\");\n return typeof received === \"string\"\n ? { _tag: \"answer_type_mismatch\", id, expected: question.type, received }\n : { _tag: \"malformed_answer\", id, reason: \"answer has no `type`\" };\n }\n\n if (question.type === \"choice\") {\n const options = Object.keys(question.criteria);\n\n if (field === \"choice\") {\n const choice = at(answer, \"choice\");\n if (typeof choice === \"string\") {\n return { _tag: \"off_criteria_choice\", id, choice, options };\n }\n }\n\n if (field === \"probabilities\") {\n const probabilities = at(answer, \"probabilities\");\n // A key the shape names and the body carries is a BAD VALUE; one the body\n // does not carry, or one the criteria do not name, is a totality failure.\n const key = issue.path[3];\n const badValue =\n isObj(probabilities) &&\n typeof key === \"string\" &&\n Object.hasOwn(probabilities, key);\n if (isObj(probabilities) && !badValue) {\n return {\n _tag: \"off_criteria_probabilities\",\n id,\n missing: options.filter((k) => !Object.hasOwn(probabilities, k)),\n extra: Object.keys(probabilities).filter((k) => !options.includes(k)),\n options,\n };\n }\n }\n }\n\n const named =\n typeof field === \"string\" ? ANSWER_FIELD_REASON[field] : undefined;\n return {\n _tag: \"malformed_answer\",\n id,\n reason: named ?? \"answer is malformed\",\n };\n};\n\n// ── parsing ──────────────────────────────────────────────\n\n/**\n * Turn an `unknown` response body into the typed answers for `questions`, or\n * into one `JevErr`. Pure, total, and never throwing on any input.\n *\n * The schema is question-driven: every id the request asked about must be\n * answered, each answer is checked against ITS question's type and — for a\n * choice — against that question's own criteria keys, both as the `choice`\n * picked and as the exact key set of `probabilities`. An answer under an id\n * nobody asked about is dropped rather than refused.\n */\nexport const parseAnswers = <Q extends JevQuestionMap>(\n questions: Q,\n body: unknown,\n): JevParse<Q> => {\n const parsed = checkResponse(questions, body);\n if (!parsed.ok) return err(toJevErr(questions, body, parsed.issue));\n const { answers, model, usage } = parsed.value;\n // The walk built `answers` id for id out of `questions`, each answer checked\n // against its own question; `Object.entries` lost the literal key types on\n // the way, and this names them back.\n return { ok: true, answers: answers as JevAnswers<Q>, model, usage };\n};\n\n// ── status classification ──────────────────────────────────────────────────\n\n/**\n * What a caller does next with an HTTP status.\n *\n * `retry` means back off and send it again; `terminal` means this request will\n * never succeed as written. `ok` is the 2xx arm — naming it keeps the function\n * total, so `ask` switches on three cases instead of testing 2xx itself and\n * then asking here.\n */\nexport type JevHttpStatus = \"ok\" | \"retry\" | \"terminal\";\n\n/**\n * Classify one HTTP status, per the reference page's error table.\n *\n * 429 (rate limited) and 529 (overloaded) are the two the page tells you to\n * back off on. 401 (bad key) and 422 (the body failed validation) will fail\n * identically forever. Every other non-2xx is terminal by default: an\n * unrecognized failure is not evidence that retrying helps, and a retry loop\n * over one is the expensive way to find that out.\n */\nexport const classifyStatus = (status: number): JevHttpStatus => {\n if (status >= 200 && status < 300) return \"ok\";\n if (status === 429 || status === 529) return \"retry\";\n return \"terminal\";\n};\n","/**\n * @packageDocumentation\n * internal/jev/ask — one Cmd that asks Jev a question map, over\n * `../../resilience/resilient-call`, with a pure fallback behind it. Internal\n * since #217 — not published on any subpath; `../protocol` is the wire contract\n * it speaks.\n *\n * This is `../../llm-call` for Jev: a config object, the resilient-call slice\n * it INHERITS, the `Cmd.define`d run Cmd, and the pure functions that turn an\n * HTTP reply into that Cmd's outcome. It ships no I/O (ADR 0021, 0022): the\n * HTTP call is the handler YOU write, in your engine's style, and it hands the\n * reply to `decode`. `init`, `attempt`, `succeed`, `onTimer`, `deadlines` and\n * `timer` are resilient-call's verbs forwarded; there is no retry loop and no\n * clock read in any verb here.\n *\n * ## The handler you write, and the one policy\n *\n * - The `resilient_run` handler calls Jev and returns\n * `ask.decode(cmd.input, { status, body })` — or `ask.rejected(cause)` when\n * the call itself threw. The key lives in your handler's closure, never in\n * config: the door never reads an env var or holds a key.\n * - `fallback?: (request) => JevAnswers<Q> | JevErr` — a PURE deterministic\n * decider. It answers on the two paths where the network cannot: a handler\n * with no key returns `ask.offline(cmd.input)`, and the resilient retry\n * budget is spent. Pure is the requirement, not a preference — the\n * exhaustion path calls it inside the `fail` verb, so a fallback that read\n * a clock or a socket would break replay.\n * - `retry: RetryPolicy` — handed straight to resilient-call. Omit it and the\n * first transient failure is terminal, exactly as resilient-call documents.\n *\n * ## Which failures back off and which do not\n *\n * `classifyStatus` (the protocol child, the single reading of the error table)\n * splits the answer three ways, and that split IS the retry decision:\n *\n * - `ok` → parse. A body that disagrees with the questions asked settles\n * `resilient_run_err` carrying the protocol's own {@link JevErr} —\n * terminal, because the same request will produce the same body.\n * - `retry` (429 / 529) → a TRANSIENT {@link JevAskErr}. `fail` feeds it to\n * resilient-call's backoff, so the call waits and re-issues.\n * - `terminal` (401 / 422 / anything else non-2xx) → a terminal\n * {@link JevAskErr}. `fail` settles it through `settleFailed`: the call\n * ends now, the breaker is not tripped and the handler is NOT run again.\n *\n * Every failure crosses the handler as the run Cmd's one declared tag,\n * `port_rejected`, with the typed {@link JevAskErr} riding on `jev`\n * ({@link JevRejected}). `fail` reads it back with {@link jevAskErrOf}.\n *\n * ## Typical wiring\n *\n * const ask = createJevAsk({\n * questions, // the map; its keys type the answers\n * fallback: (req) => offlineGuess(req),\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * cmds: [ask.run],\n * init: () => [{ resilience: ask.init(), answers: null, failure: null }, []],\n * update: {\n * ask_jev: (s, m) => liftJevAsk(s, ask.attempt(s.resilience, m.key, m.state, m.at)),\n * resilient_run_ok: (s, m) => …ask.succeed(s.resilience, m)…,\n * resilient_run_err: (s, m) => …ask.fail(s.resilience, m)…,\n * deadline_exceeded: (s, m) => liftJevAsk(s, ask.onTimer(s.resilience, m)),\n * },\n * subs: [{ type: \"timer\", deps: (s) => ask.timer(s.resilience) }],\n *\n * // and where it runs — the HTTP call is your handler:\n * run(machine, {\n * interpret: {\n * resilient_run: async (cmd) => {\n * try { return ask.decode(cmd.input, await callJev(apiKey, cmd.input)); }\n * catch (cause) { return ask.rejected(cause); }\n * },\n * },\n * });\n *\n * ## Naming a knob, when a machine holds more than one\n *\n * Two unnamed knobs both speak `resilient_run` / `resilient_run_ok` /\n * `resilient_run_err`, so their handlers and settle cells collide. `name`\n * renames the whole family for one knob. It is resilient-call's own `name`,\n * forwarded:\n *\n * const judge = createJevAsk({ questions: judgeQuestions, name: \"judge\" });\n * const sort = createJevAsk({ questions: sortQuestions, name: \"sort\" });\n *\n * cmds: [judge.run, sort.run],\n * update: {\n * judge_run_ok: (s, m) => …, // m.value is JevOk<JudgeQuestions>\n * sort_run_ok: (s, m) => …, // m.value is JevOk<SortQuestions>\n * judge_deadline: (s, m) => …, // a named knob's timer Msg\n * },\n *\n * Both type parameters infer from the config. Omit `name` and every string\n * above is `resilient*`, with `deadline_exceeded` as the timer Msg.\n */\n\nimport { liftSlice } from \"../../../compose\";\nimport { describeError } from \"../../../describe-error\";\nimport { type Cmd, Outcome } from \"../../../index\";\nimport type { RetryPolicy } from \"../../../retry-backoff\";\nimport type { DeadlineSub } from \"../../resilience/deadline\";\nimport {\n createResilientCall,\n type DeadlineNameOf,\n type DefaultResilientName,\n type FailMsg,\n type ResilientConfig,\n type ResilientState,\n type ResilientTimerMsg,\n type RunCmd,\n runCmdDef,\n type SucceedMsg,\n} from \"../../resilience/resilient-call\";\nimport {\n classifyStatus,\n isJevErr,\n type JevAnswers,\n type JevErr,\n type JevQuestionMap,\n type JevRequest,\n type JevState,\n type JevUsage,\n parseAnswers,\n} from \"../protocol\";\n\n// ===========================================================================\n// The reply your handler hands back, and the fallback.\n// ===========================================================================\n\n/**\n * What an HTTP call to Jev came back with: the status and the undecoded body.\n *\n * Your handler returns this to {@link decodeJevReply} rather than throwing on a\n * non-2xx, because WHICH non-2xx it is decides whether the call backs off, and\n * a thrown error has already lost that.\n */\nexport interface JevHttpReply {\n readonly status: number;\n readonly body: unknown;\n}\n\n/**\n * The pure decider that answers when the network cannot.\n *\n * Called on exactly two paths: a handler with no key returns\n * {@link offlineJevAnswer}, and the resilient retry budget is spent. It must\n * be a pure function of the request — the exhaustion path runs it inside the\n * `fail` VERB, which replay re-runs, so a clock read or an I/O call here would\n * make the machine unreplayable. Returning a {@link JevErr} is how a fallback\n * says \"I have no answer for this one\"; the call then settles as that error.\n */\nexport type JevFallback<Q extends JevQuestionMap> = (\n request: JevRequest<Q>,\n) => JevAnswers<Q> | JevErr;\n\n// ===========================================================================\n// Failures — the protocol's, plus the three this door adds.\n// ===========================================================================\n\n/**\n * Every way an ask can fail, as DATA.\n *\n * A superset of the protocol child's {@link JevErr}: a parse failure is\n * reported with the protocol's own tag untouched, and the tags below are the\n * ones only a caller can observe. `{_tag}`-discriminated and\n * JSON-round-trippable like the rest (ADR 0011), because it is carried on the\n * settle Msg and stored in the slice's `failed` phase.\n *\n * `http_retry` and `port_threw` are the TRANSIENT pair — {@link isTransientJevAskErr}\n * is the single reading, and `fail` backs off on exactly those two.\n */\nexport type JevAskErr =\n | JevErr\n /** 429 / 529 — the statuses `classifyStatus` says to back off on. */\n | { readonly _tag: \"http_retry\"; readonly status: number }\n /** 401 / 422 / any other non-2xx — this request fails identically forever. */\n | { readonly _tag: \"http_terminal\"; readonly status: number }\n /** The call itself threw (a socket error, a DNS failure) — transient. */\n | { readonly _tag: \"port_threw\"; readonly reason: string }\n /** No key and no `fallback`: the knob was configured with no way to answer. */\n | { readonly _tag: \"no_answer_path\" };\n\n/**\n * Does this failure deserve another attempt?\n *\n * The one place the transient/terminal split is read. A rate limit and a dropped\n * socket are the two failures a second attempt can clear; a 401, a 422 and every\n * parse failure will reproduce exactly, so retrying one spends a real HTTP call\n * to learn nothing.\n */\nexport function isTransientJevAskErr(error: JevAskErr): boolean {\n return error._tag === \"http_retry\" || error._tag === \"port_threw\";\n}\n\n/**\n * How a {@link JevAskErr} crosses the handler: the run Cmd's declared\n * `port_rejected` tag, with the typed error on `jev`. The engine mints it into\n * `resilient_run_err` untouched, and {@link jevAskErrOf} reads it back.\n */\nexport type JevRejected = {\n readonly _tag: \"port_rejected\";\n readonly jev: JevAskErr;\n};\n\n/** Build the {@link JevRejected} outcome for one {@link JevAskErr}. PURE. */\nfunction reject(jev: JevAskErr): Outcome<never, JevRejected> {\n return Outcome.err({ _tag: \"port_rejected\", jev });\n}\n\n/**\n * Read a settled failure back as a {@link JevAskErr}. PURE.\n *\n * A {@link JevRejected} yields its `jev`. Anything else a handler returned —\n * a bare `port_rejected`, a `deadline_exceeded` — is a failure this door did\n * not classify, and it reads as the transient `port_threw`, exactly as a\n * foreign throw always has.\n */\nexport function jevAskErrOf(error: unknown): JevAskErr {\n if (typeof error === \"object\" && error !== null) {\n const jev = (error as { readonly jev?: unknown }).jev;\n if (\n typeof jev === \"object\" &&\n jev !== null &&\n typeof (jev as { readonly _tag?: unknown })._tag === \"string\"\n ) {\n return jev as JevAskErr;\n }\n }\n return { _tag: \"port_threw\", reason: describeError(error) };\n}\n\n// ===========================================================================\n// Config, request and result.\n// ===========================================================================\n\n/** The model the door asks for when config names none. */\nexport const DEFAULT_JEV_MODEL = \"jev-latest\";\n\n/** No tokens were spent — what a fallback answer's usage is. */\nconst NO_USAGE: JevUsage = { input_tokens: 0, output_tokens: 0 };\n\n/**\n * The jev-ask knob. `questions` is the load-bearing one: it is both the map\n * sent on every request and the type that makes the answers narrow at the call\n * site, so it belongs to the knob rather than to a call.\n */\nexport interface JevAskConfig<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> {\n /** The question map every request carries; its keys type the answers. */\n readonly questions: Q;\n /**\n * What this knob's Cmd and Msgs are called: `<name>_run`, `<name>_run_ok`,\n * `<name>_run_err` and the timer Msg `<name>_deadline`. Omit it and they are\n * `resilient_run`, `resilient_run_ok`, `resilient_run_err` and\n * `deadline_exceeded`. Name a knob when a machine holds more than one, so\n * each settles into its own `update` cell with its own answer type.\n */\n readonly name?: N;\n /** The pure decider for the no-key and budget-spent paths. Absent → those settle as an error. */\n readonly fallback?: JevFallback<Q>;\n /** Backoff policy, composed into `../../resilience/resilient-call`. Omit → no backoff. */\n readonly retry?: RetryPolicy;\n /** The model id asked for. Defaults to {@link DEFAULT_JEV_MODEL}. */\n readonly model?: string;\n}\n\n/**\n * The settled answer carried on `resilient_run_ok`.\n *\n * `source` is not decoration: a fallback answer and a model answer are both\n * valid answers with identical shape, and a host that routes on confidence — or\n * bills on usage — has to be able to tell them apart. A fallback's `usage` is\n * zero because nothing was spent.\n */\nexport interface JevOk<Q extends JevQuestionMap> {\n /** The typed answers, one per question id. */\n readonly answers: JevAnswers<Q>;\n /** The model that answered, as the response reported it (or the requested one, for a fallback). */\n readonly model: string;\n /** Tokens spent. Zero on the fallback path. */\n readonly usage: JevUsage;\n /** Which path produced this answer. */\n readonly source: \"port\" | \"fallback\";\n}\n\n// ===========================================================================\n// The pure outcome builders your handler returns.\n// ===========================================================================\n\n/**\n * A fallback's answer or its refusal.\n *\n * The discriminant is the protocol child's own {@link isJevErr} — the `_tag`\n * VALUE read against the closed `JevErr` tag set. Testing for the KEY instead\n * reads a caller's question ids as a discriminant they never agreed to: a\n * question legally named `_tag` makes `\"_tag\" in decided` true on a perfectly\n * good answers map, and the successful fallback then settles as a refusal\n * carrying the answers map as its error. An answer is always an object and\n * never one of those five tag literals, so the value test cannot collide.\n */\nfunction isErrDecision<Q extends JevQuestionMap>(\n decided: JevAnswers<Q> | JevErr,\n): decided is JevErr {\n return isJevErr(decided);\n}\n\n/**\n * Turn an HTTP reply into the run Cmd's outcome. PURE: classify the status,\n * then parse the body against the questions the request carried.\n *\n * `ok` → the typed {@link JevOk}; a 429 / 529 → the transient `http_retry`;\n * any other non-2xx → the terminal `http_terminal`; a body that disagrees with\n * the questions → the protocol's own {@link JevErr}. Every failure is a\n * {@link JevRejected}.\n */\nexport function decodeJevReply<Q extends JevQuestionMap>(\n request: JevRequest<Q>,\n reply: JevHttpReply,\n): Outcome<JevOk<Q>, JevRejected> {\n const kind = classifyStatus(reply.status);\n if (kind === \"retry\")\n return reject({ _tag: \"http_retry\", status: reply.status });\n if (kind === \"terminal\") {\n return reject({ _tag: \"http_terminal\", status: reply.status });\n }\n const parsed = parseAnswers(request.questions, reply.body);\n if (!parsed.ok) return reject(parsed.error);\n return Outcome.ok({\n answers: parsed.answers,\n model: parsed.model,\n usage: parsed.usage,\n source: \"port\",\n });\n}\n\n/**\n * The outcome for a call that threw before Jev answered — a socket error, a\n * DNS failure. PURE. It is the transient `port_threw`, so it backs off.\n */\nexport function jevCallThrew(cause: unknown): Outcome<never, JevRejected> {\n return reject({ _tag: \"port_threw\", reason: describeError(cause) });\n}\n\n/**\n * The outcome with no network at all: the fallback's answer, or its refusal,\n * or `no_answer_path` when there is no fallback. PURE — what a handler with\n * no key returns.\n */\nexport function offlineJevAnswer<Q extends JevQuestionMap>(\n request: JevRequest<Q>,\n fallback: JevFallback<Q> | undefined,\n): Outcome<JevOk<Q>, JevRejected> {\n if (fallback === undefined) return reject({ _tag: \"no_answer_path\" });\n const decided = fallback(request);\n if (isErrDecision(decided)) return reject(decided);\n return Outcome.ok({\n answers: decided,\n model: request.model,\n usage: NO_USAGE,\n source: \"fallback\",\n });\n}\n\n// ===========================================================================\n// The Cmd + Msgs the knob speaks.\n// ===========================================================================\n\n/**\n * The one effect this knob emits: run the ask for `key` with the plain-data\n * {@link JevRequest} (ADR 0014 — a Cmd is DECLARED, so its input, ok and err\n * channels are types on the constructor rather than a convention). It is\n * resilient-call's `runCmdDef` specialized to this door's input and result, the\n * same way `../../idempotency/idempotent-intake` mints its defs per knob: `Q` is\n * a type parameter, so the def is a factory and not a module constant. `name`\n * is the knob's name; omit it for the `resilient` family.\n */\nexport function jevAskCmdDef<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n>(name?: N) {\n return runCmdDef<JevRequest<Q>, JevOk<Q>, N>(name);\n}\n\n/** The Cmd the verbs emit — `<name>_run` carrying the request. */\nexport type JevAskCmd<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> = RunCmd<JevRequest<Q>, N, JevOk<Q>>;\n\n/** The success settle Msg — resilient-call's, with `value` narrowed to {@link JevOk}. */\nexport type JevSucceedMsg<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> = SucceedMsg<JevOk<Q>, N>;\n\n/**\n * The failure settle Msg the engine mints — resilient-call's. Its `error` is a\n * {@link JevRejected} when your handler used this door's builders; `fail`\n * reads it with {@link jevAskErrOf}.\n */\nexport type JevFailMsg<N extends string = DefaultResilientName> = FailMsg<N>;\n\n/**\n * The retry / deadline timer Msg — resilient-call's. `deadline_exceeded` for an\n * unnamed knob, `<name>_deadline` for a named one.\n */\nexport type JevTimerMsg<N extends string = DefaultResilientName> =\n ResilientTimerMsg<N>;\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a jev-ask knob from `config`. `rng` is injected for the inherited retry\n * jitter (pass a fixed `() => 0` in tests to pin backoff; defaults to\n * `Math.random`, read only at resilient-call's verb boundary).\n *\n * The slice and most verbs are resilient-call's, forwarded. `fail` is the one\n * composed verb — it reads the {@link JevAskErr} back off the settled Msg and\n * routes on it (see its docblock); it reimplements no backoff. `decode`,\n * `rejected` and `offline` are the pure outcome builders your handler returns.\n */\nexport function createJevAsk<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n>(config: JevAskConfig<Q, N>, rng: () => number = Math.random) {\n const resilientConfig: ResilientConfig<N> = {\n ...(config.name === undefined ? {} : { name: config.name }),\n ...(config.retry === undefined ? {} : { retry: config.retry }),\n };\n const rc = createResilientCall<JevRequest<Q>, JevOk<Q>, N>(\n resilientConfig,\n rng,\n );\n const model = config.model ?? DEFAULT_JEV_MODEL;\n\n /** The slice this knob owns — resilient-call's slice verbatim. */\n type State = ResilientState<JevRequest<Q>, JevOk<Q>>;\n\n /** The request a call under `key` is live with, or `undefined` if it is not live. */\n function liveRequest(s: State, key: string): JevRequest<Q> | undefined {\n const call = s.calls[key];\n if (call?.phase === \"running\" || call?.phase === \"waiting_retry\") {\n return call.input;\n }\n return undefined;\n }\n\n /** The starting slice — resilient-call's. */\n function init(): State {\n return rc.init();\n }\n\n /**\n * Ask the configured questions about `content` under `key`. PURE — it builds\n * the plain-data request (no closures: `state`, `model` and `questions` are\n * all values) and delegates straight to resilient-call's gate.\n */\n function attempt(\n s: State,\n key: string,\n content: JevState,\n at: number,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const request: JevRequest<Q> = {\n state: content,\n model,\n questions: config.questions,\n };\n return rc.attempt(s, key, request, at);\n }\n\n /** Record a settled answer. PURE — resilient-call's `settle`. */\n function succeed(\n s: State,\n msg: JevSucceedMsg<Q, N>,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const { call, cmds } = rc.settle(s, msg);\n return [call, cmds];\n }\n\n /**\n * Settle `key` from the fallback, as if the fallback's answer were the\n * response. PURE — the fallback is a pure decider, and `settle` /\n * `settleFailed` are resilient-call's own settle verbs. `s` is the state\n * BEFORE the failing attempt was recorded, so the call is still live and\n * `settle` closes it normally.\n */\n function settleFromFallback(\n s: State,\n key: string,\n request: JevRequest<Q>,\n fallback: JevFallback<Q>,\n at: number,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const decided = offlineJevAnswer(request, fallback);\n if (decided._tag === \"Err\") {\n return rc.settleFailed(s, key, decided.error.jev, at);\n }\n const { call, cmds } = rc.settle(s, {\n cmd: { key },\n value: decided.value,\n at,\n });\n return [call, cmds];\n }\n\n /**\n * Record a failure. PURE. The backoff itself is resilient-call's — nothing\n * here recomputes a delay, counts an attempt or reads a clock. What this verb\n * adds is the routing the typed error makes possible, and it is two\n * decisions:\n *\n * 1. A TERMINAL error settles through `settleFailed`, which ends the call\n * without touching the breaker or the retry counter. Handing a 401 or a\n * parse failure to `settle` instead would re-issue the identical request\n * and trip a breaker over a backend that is perfectly healthy.\n * 2. When the transient path EXHAUSTS the retry budget and a `fallback` is\n * configured, the fallback answers instead of the call settling failed.\n * This is the one place that can be observed — exhaustion is a fact of\n * the slice, not of the handler, so the handler cannot know it is on the\n * last attempt.\n *\n * The slice's `failed` phase stores the typed {@link JevAskErr}, never the\n * {@link JevRejected} carrier.\n */\n function fail(\n s: State,\n msg: JevFailMsg<N>,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const { key } = msg.cmd;\n const error = jevAskErrOf(msg.error);\n if (!isTransientJevAskErr(error)) {\n return rc.settleFailed(s, key, error, msg.at);\n }\n const request = liveRequest(s, key);\n const { call, cmds, outcome } = rc.settle(s, { ...msg, error });\n if (\n outcome.kind !== \"failed\" ||\n config.fallback === undefined ||\n request === undefined\n ) {\n return [call, cmds];\n }\n return settleFromFallback(s, key, request, config.fallback, msg.at);\n }\n\n /** A retry / deadline timer fired. PURE — resilient-call's `onTimer`. */\n function onTimer(\n s: State,\n msg: JevTimerMsg<N>,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n return rc.onTimer(s, msg);\n }\n\n /** The call's deadlines — resilient-call's retry and deadline timers. */\n function deadlines(s: State): readonly DeadlineSub<DeadlineNameOf<N>>[] {\n return rc.deadlines(s);\n }\n\n return {\n name: rc.name,\n /** The `Cmd.define`d run Cmd — list it in the machine's `cmds`. */\n run: rc.run,\n init,\n attempt,\n succeed,\n fail,\n onTimer,\n deadlines,\n /** The built-in `timer` Sub's deps — resilient-call's `timer`. */\n timer: rc.timer,\n /** Your handler's outcome for an HTTP reply — {@link decodeJevReply}. */\n decode: (request: JevRequest<Q>, reply: JevHttpReply) =>\n decodeJevReply(request, reply),\n /** Your handler's outcome when the call threw — {@link jevCallThrew}. */\n rejected: jevCallThrew,\n /** Your handler's outcome with no network — {@link offlineJevAnswer}. */\n offline: (request: JevRequest<Q>) =>\n offlineJevAnswer(request, config.fallback),\n };\n}\n\n/**\n * The Cmd type a host machine declares in `types.cmd` when it splices a\n * {@link createJevAsk} knob in — {@link JevAskCmd} under the name a `types`\n * block reads well with. It is stated here so a host writes `cmd: {} as\n * JevCmd<Questions>` rather than deriving it from the knob's shape.\n */\nexport type JevCmd<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> = JevAskCmd<Q, N>;\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the slice\n * lives at `state.resilience` — resilient-call's convenience, re-typed for this\n * door's slice so a consumer wires one import. Pure.\n *\n * The record rebuild is `liftSlice` from the root door (#231), the same helper\n * `liftResilience` is expressed over — this stays as the named, pre-keyed\n * convenience for this door, unchanged in name, signature and subpath.\n */\nexport function liftJevAsk<\n S extends { resilience: ResilientState<JevRequest<Q>, JevOk<Q>> },\n Q extends JevQuestionMap,\n C extends Cmd,\n>(\n state: S,\n result: readonly [ResilientState<JevRequest<Q>, JevOk<Q>>, readonly C[]],\n): readonly [S, readonly C[]] {\n return liftSlice(\"resilience\", state, result);\n}\n\nexport type { ResilientState };\n","/**\n * @packageDocumentation\n * internal/jev/classify-batch — the composition that turns a stream of items\n * into Jev calls. Internal since #218 — not published on any subpath.\n *\n * It writes NO chunker, NO cache, NO concurrency limiter and NO retry. Every\n * one of those already exists as a battery, and this module is the wiring:\n *\n * - `../../flow/batch-window` coalesces `add(item, at)` calls into size- or\n * time-bounded batches (`maxItems`, `maxMs`).\n * - `../../flow/fan-out` bounds how many batches are in flight\n * (`concurrency`), keyed by a batch id that is a pure function of the\n * batch's item keys.\n * - `../../resilience/cache` is the TTL cache keyed by `keyOf(item)`. An item\n * whose key is cached never enters a batch; every settled answer is written\n * back with `ttlMs`.\n * - `../ask` is the one effect: each flushed batch becomes one `resilient_run`\n * Cmd (`Cmd.define`d; its handler is yours, and hands the HTTP reply to\n * `decode`) carrying Jev's NATIVE batch form (grill #215, R1.2) — the item array\n * IS the request `state`, and `questions` holds one `choice` question per\n * item, keyed by `keyOf(item)`. There is no `{ transactions }` envelope of\n * any kind.\n *\n * ## The slice is plain data, and time arrives as `at`\n *\n * {@link ClassifyBatchState} is the three batteries' own slices plus a failure\n * map, so it survives a `Store` rehydrate unchanged. Every transition is a Msg\n * through the verbs (`add`, `onWindow`, `onBatchOk`, `onBatchErr`, `onEvict`);\n * no verb reads a clock — a settled batch carries the `at` the engine stamped.\n *\n * ## What this module deliberately does not decide\n *\n * `answerFor` is a derived READ. It reports `answered` / `failed` / `pending` /\n * `absent` and hands back the choice answer with its own `confidence`; whether\n * a given confidence auto-assigns or goes to triage is the HOST reducer's rule,\n * and putting a threshold here would make one caller's policy everybody's.\n *\n * ## Typical wiring\n *\n * const classify = createClassifyBatch({\n * keyOf: (t) => sanitizedMerchant(t),\n * criteria: { groceries: \"Supermarkets\", dining: \"Restaurants\" },\n * maxItems: 25,\n * maxMs: 2_000,\n * concurrency: 4,\n * ttlMs: 86_400_000,\n * });\n *\n * cmds: [classify.ask],\n * update: {\n * classify_add: (s, m) => lift(s, classify.add(s.classify, m.item, m.at)),\n * deadline_exceeded: (s, m) => lift(s, classify.onWindow(s.classify, m.atMs)),\n * resilient_run_ok: (s, m) => lift(s, classify.onBatchOk(s.classify, m)),\n * resilient_run_err: (s, m) => lift(s, classify.onBatchErr(s.classify, m)),\n * cache_evict: (s, m) => lift(s, classify.onEvict(s.classify, nowFromMsg)),\n * },\n * subs: classify.subEntries((s: Model) => s.classify),\n *\n * // and where it runs — the HTTP call is your handler:\n * run(machine, {\n * interpret: {\n * resilient_run: async (cmd) => {\n * try { return classify.decode(cmd.input, await callJev(apiKey, cmd.input)); }\n * catch (cause) { return classify.rejected(cause); }\n * },\n * },\n * subscribe: classify.subscribers(),\n * });\n */\n\nimport { liftSlice, type ReadStep, readInOrder } from \"../../../compose\";\nimport type { Cmd, DepKeyedSub, TimerSub } from \"../../../index\";\nimport {\n type BatchWindow,\n type BatchWindowExpired,\n type BatchWindowSub,\n createBatchWindow,\n} from \"../../flow/batch-window\";\nimport { createFanOut, type FanOutState } from \"../../flow/fan-out\";\nimport {\n type CacheEvictionSub,\n cacheEvictionSub,\n cacheEvictionSubscribe,\n get as cacheGet,\n has as cacheHas,\n set as cacheSet,\n evictExpired,\n initCache,\n type TtlCache,\n} from \"../../resilience/cache\";\nimport {\n DEFAULT_JEV_MODEL,\n decodeJevReply,\n type JevAskCmd,\n type JevAskErr,\n type JevFailMsg,\n type JevFallback,\n type JevHttpReply,\n type JevOk,\n type JevSucceedMsg,\n jevAskCmdDef,\n jevAskErrOf,\n jevCallThrew,\n offlineJevAnswer,\n} from \"../ask\";\nimport type {\n JevAnswerFor,\n JevChoiceQuestion,\n JevRequest,\n JevText,\n} from \"../protocol\";\n\n// ===========================================================================\n// The question shape one item gets.\n// ===========================================================================\n\n/**\n * The rubric every item is classified against: option key → description, or\n * `null` where an option needs no extra detail. `C` is the option union, and it\n * is what makes an answer's `choice` narrow at the call site.\n */\nexport type ClassifyCriteria<C extends string> = Readonly<\n Record<C, JevText | null>\n>;\n\n/** The one `choice` question an item is asked. */\nexport type ItemQuestion<C extends string> = JevChoiceQuestion<\n ClassifyCriteria<C>\n>;\n\n/**\n * The `questions` map of one batch: one {@link ItemQuestion} per item, keyed by\n * `keyOf(item)`. This IS Jev's native batch form — the questions map is the\n * fan-out over items, so nothing here wraps the items in an envelope of its own.\n */\nexport type ItemQuestions<C extends string> = Readonly<\n Record<string, ItemQuestion<C>>\n>;\n\n/** The answer one item's question yields. */\nexport type ItemAnswer<C extends string> = JevAnswerFor<ItemQuestion<C>>;\n\n// ===========================================================================\n// Batches, Cmds and Msgs.\n// ===========================================================================\n\n/**\n * One flushed batch, as plain data: the items and the id fan-out addresses it\n * by. `id` is a pure function of the batch's item keys (they are joined with a\n * separator no sanitized key contains), which is what lets a settle Msg — whose\n * `key` is that id — find its batch again after a rehydrate.\n */\nexport interface Batch<I> {\n readonly id: string;\n readonly items: readonly I[];\n}\n\n/** The Cmd a flushed batch becomes — `../ask`'s, carrying the native request. */\nexport type ClassifyBatchCmd<C extends string> = JevAskCmd<ItemQuestions<C>>;\n\n/** The success settle Msg — `../ask`'s, with the batch's answers. */\nexport type ClassifyBatchOkMsg<C extends string> = JevSucceedMsg<\n ItemQuestions<C>\n>;\n\n/** The failure settle Msg — `../ask`'s, with the typed {@link JevAskErr}. */\nexport type ClassifyBatchErrMsg = JevFailMsg;\n\n/**\n * The internal Cmd the batch window flushes into. It never leaves this module:\n * `add` and `onWindow` intercept it and hand the items to fan-out, which is\n * what bounds how many batches are in flight. Letting the window emit the ask\n * Cmd directly would flush straight past `concurrency`.\n */\ntype FlushCmd<I> = Cmd<\"classify_batch_flush\"> & {\n readonly items: readonly I[];\n};\n\n// ===========================================================================\n// Config and slice.\n// ===========================================================================\n\n/** The default page size — the one the grill settled on for Jev. */\nexport const DEFAULT_CLASSIFY_MAX_ITEMS = 25;\n\n/** The knob. Every field is a policy this module refuses to invent. */\nexport interface ClassifyBatchConfig<I, C extends string> {\n /**\n * The cache key and the question id of an item — e.g. a sanitized merchant\n * string, so two transactions at the same shop share one answer. PURE.\n */\n readonly keyOf: (item: I) => string;\n /** The rubric every item is classified against. Its keys are the answer domain. */\n readonly criteria: ClassifyCriteria<C>;\n /**\n * The `instructions` for one item's question. Defaults to a template naming\n * the item's key, which is what tells the model WHICH element of the request\n * `state` the question is about.\n */\n readonly instructions?: (key: string, item: I) => JevText;\n /** Size trigger, forwarded to the batch window. Defaults to {@link DEFAULT_CLASSIFY_MAX_ITEMS}. */\n readonly maxItems?: number;\n /** Latency ceiling in ms, forwarded to the batch window. */\n readonly maxMs: number;\n /** Max batches in flight, forwarded to fan-out. */\n readonly concurrency: number;\n /** How long a settled answer stays cached, forwarded to the TTL cache. */\n readonly ttlMs: number;\n /** Eviction tick for the TTL cache. Omit and no eviction Sub is declared. */\n readonly evictEveryMs?: number;\n /** The model asked for. Forwarded to `../ask`'s default when omitted. */\n readonly model?: string;\n /** The pure decider `offline` answers with, for a handler with no key. */\n readonly fallback?: JevFallback<ItemQuestions<C>>;\n}\n\n/**\n * The slice: the three batteries' own slices, plus the one fact none of them\n * holds — which keys a failed batch left unanswered. All plain data.\n */\nexport interface ClassifyBatchState<I, C extends string> {\n /** The open batch window — `../../flow/batch-window`'s slice. */\n readonly window: BatchWindow<I>;\n /** The in-flight batches — `../../flow/fan-out`'s slice. */\n readonly fanOut: FanOutState<Batch<I>, JevOk<ItemQuestions<C>>>;\n /** The answers, keyed by `keyOf` — `../../resilience/cache`'s slice. */\n readonly cache: TtlCache<ItemAnswer<C>>;\n /** Keys whose batch settled `resilient_run_err`, with the error that settled it. */\n readonly failed: Readonly<Record<string, JevAskErr>>;\n}\n\n/** What {@link ClassifyBatchKnob.answerFor} reports about one key. */\nexport type KeyAnswer<C extends string> =\n /** Answered and unexpired: the host applies its own confidence rule to this. */\n | { readonly status: \"answered\"; readonly answer: ItemAnswer<C> }\n /** The batch carrying this key settled `resilient_run_err`. Nothing was cached. */\n | { readonly status: \"failed\"; readonly error: JevAskErr }\n /** Buffered in the open window, or in a batch that has not settled. */\n | { readonly status: \"pending\" }\n /** Never seen, or cached and now expired. */\n | { readonly status: \"absent\" };\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a classify-batch knob from `config`.\n *\n * The verbs are pure and the whole composition is the four batteries'; the only\n * thing this factory adds is the native-batch request shape and the bookkeeping\n * that keeps one key out of two batches at once.\n */\nexport function createClassifyBatch<I, C extends string>(\n config: ClassifyBatchConfig<I, C>,\n) {\n type State = ClassifyBatchState<I, C>;\n type AskOk = JevOk<ItemQuestions<C>>;\n type Cmds = readonly ClassifyBatchCmd<C>[];\n\n const maxItems = config.maxItems ?? DEFAULT_CLASSIFY_MAX_ITEMS;\n const askCmd = jevAskCmdDef<ItemQuestions<C>>();\n const instructionsFor =\n config.instructions ??\n ((key: string): JevText =>\n `Classify the element of \\`state\\` whose key is \"${key}\".`);\n\n /** The batch's identity: its item keys, in arrival order. PURE. */\n function idOfBatch(batch: Batch<I>): string {\n return batch.id;\n }\n\n /** One batch's `questions` map — one choice question per item, keyed by `keyOf`. */\n function questionsFor(items: readonly I[]): ItemQuestions<C> {\n const questions: Record<string, ItemQuestion<C>> = {};\n for (const item of items) {\n const key = config.keyOf(item);\n questions[key] = {\n type: \"choice\",\n instructions: instructionsFor(key, item),\n criteria: config.criteria,\n };\n }\n return questions;\n }\n\n /**\n * The native Jev request for a batch: the item array IS the `state`, and the\n * questions map is the per-item fan-out. No envelope.\n */\n function requestFor(batch: Batch<I>): JevRequest<ItemQuestions<C>> {\n return {\n state: batch.items,\n model: config.model ?? DEFAULT_JEV_MODEL,\n questions: questionsFor(batch.items),\n };\n }\n\n const fanOut = createFanOut<\n Batch<I>,\n AskOk,\n ClassifyBatchCmd<C>,\n ClassifyBatchCmd<C>\n >({\n concurrency: config.concurrency,\n idOf: idOfBatch,\n of: (batch) => askCmd({ key: batch.id, input: requestFor(batch) }),\n });\n\n const window = createBatchWindow<I, FlushCmd<I>>({\n maxItems,\n maxMs: config.maxMs,\n flush: (items) => ({ type: \"classify_batch_flush\", items }),\n });\n\n /** The starting slice — each battery's own `init`, side by side. */\n function init(): State {\n return {\n window: window.init(),\n fanOut: fanOut.init(),\n cache: initCache<ItemAnswer<C>>(),\n failed: {},\n };\n }\n\n /** Every key currently buffered or in flight. PURE. */\n function keysInFlight(state: State): ReadonlySet<string> {\n const keys = new Set<string>();\n for (const item of state.window.buffer) keys.add(config.keyOf(item));\n for (const batch of state.fanOut.pending) {\n for (const item of batch.items) keys.add(config.keyOf(item));\n }\n for (const batch of state.fanOut.running) {\n for (const item of batch.items) keys.add(config.keyOf(item));\n }\n return keys;\n }\n\n /** The running batch a settle Msg's `key` addresses, or `undefined`. PURE. */\n function runningBatch(state: State, id: string): Batch<I> | undefined {\n return state.fanOut.running.find((batch) => batch.id === id);\n }\n\n /**\n * Hand a flushed window's items to fan-out. Fan-out returns the launch Cmds —\n * at most `concurrency` of them — so a window that flushes while the budget is\n * spent parks its batch in `pending` and launches nothing.\n */\n function scatter(\n state: State,\n flushed: readonly FlushCmd<I>[],\n ): readonly [State, Cmds] {\n if (flushed.length === 0) return [state, []];\n const batches = flushed.map((cmd) => ({\n id: cmd.items.map(config.keyOf).join(\"\\u0000\"),\n items: cmd.items,\n }));\n return liftSlice(\"fanOut\", state, fanOut.scatter(state.fanOut, batches));\n }\n\n /**\n * Offer `item` to the pipeline at `at`. PURE.\n *\n * An item whose key is cached, buffered, or already in a batch is DROPPED:\n * the cache hit is the whole point of the cache, and the other two are what\n * keeps one key out of two batches at once — which is the property that makes\n * a batch's id a usable identity and an answer's key unambiguous.\n *\n * An item that is NOT dropped loses its standing failure mark, exactly as\n * {@link onBatchOk} clears one: from the instant the key is buffered it is in\n * flight again, and a `failed` entry left standing would make `answerFor`\n * report the PREVIOUS attempt's error for the whole of the new one — telling a\n * host that polls it to stop waiting for work that is running.\n */\n function add(state: State, item: I, at: number): readonly [State, Cmds] {\n const key = config.keyOf(item);\n if (cacheHas(state.cache, key, at)) return [state, []];\n if (keysInFlight(state).has(key)) return [state, []];\n\n const failed: Record<string, JevAskErr> = { ...state.failed };\n delete failed[key];\n const [buffered, flushed] = liftSlice(\n \"window\",\n { ...state, failed },\n window.add(state.window, item, at),\n );\n return scatter(buffered, flushed);\n }\n\n /** The time window closed: flush whatever is buffered. PURE. */\n function onWindow(state: State, at: number): readonly [State, Cmds] {\n const [closed, flushed] = liftSlice(\n \"window\",\n state,\n window.onWindow(state.window, at),\n );\n return scatter(closed, flushed);\n }\n\n /**\n * A batch settled OK. PURE.\n *\n * Every answer is written back to the cache with `ttlMs` at `msg.at`, the\n * batch's keys lose any standing failure mark, and fan-out backfills the freed\n * concurrency slot — so the returned Cmds are the next batches' asks.\n *\n * An answer arrives under the question id it was asked under, which is the\n * item's own key, so the write-back needs no positional matching between the\n * request `state` array and the answers map.\n */\n function onBatchOk(\n state: State,\n msg: ClassifyBatchOkMsg<C>,\n ): readonly [State, Cmds] {\n let cache = state.cache;\n for (const [key, answer] of Object.entries(msg.value.answers)) {\n cache = cacheSet(cache, key, answer, msg.at, config.ttlMs);\n }\n const failed: Record<string, JevAskErr> = { ...state.failed };\n for (const key of Object.keys(msg.value.answers)) delete failed[key];\n\n return liftSlice(\n \"fanOut\",\n { ...state, cache, failed },\n fanOut.itemOk(state.fanOut, msg.cmd.key, msg.value),\n );\n }\n\n /**\n * A batch settled `resilient_run_err`. PURE.\n *\n * Each of its keys is marked failed with the error that settled it and the\n * cache is NOT touched: a failure is not an answer, and writing one back under\n * `ttlMs` would poison every later `add` for that key for the whole TTL. The\n * key stays re-addable, because it is neither cached nor in flight once\n * fan-out has moved the batch to `failed`.\n */\n function onBatchErr(\n state: State,\n msg: ClassifyBatchErrMsg,\n ): readonly [State, Cmds] {\n const batch = runningBatch(state, msg.cmd.key);\n const error = jevAskErrOf(msg.error);\n const failed: Record<string, JevAskErr> = { ...state.failed };\n if (batch !== undefined) {\n for (const item of batch.items) failed[config.keyOf(item)] = error;\n }\n return liftSlice(\n \"fanOut\",\n { ...state, failed },\n fanOut.itemErr(state.fanOut, msg.cmd.key, error),\n );\n }\n\n /** The eviction tick fired: drop every entry expired at `at`. PURE. */\n function onEvict(state: State, at: number): readonly [State, Cmds] {\n return liftSlice(\"cache\", state, [evictExpired(state.cache, at), []]);\n }\n\n /**\n * The precedence of {@link answerFor}'s composed read, as DATA — the order\n * the three battery slices are consulted in, cache first.\n *\n * Written as a value rather than as three `if` statements because this read\n * is where the composition's one escaped defect lived: a key whose batch\n * failed and which the host then re-added is IN FLIGHT, and reading `failed`\n * before the in-flight slices told a polling host to stop waiting for work\n * that was running (`bec6545`, criterion 7 of #214). As a value the order is\n * nameable and directly assertable — the per-battery suites cannot see it at\n * all.\n *\n * `cache` first: an unexpired answer is the strongest fact any slice holds.\n * `failed` next: a standing mark means the last attempt ended and none has\n * started since, because every path that starts one clears the mark. The\n * in-flight slices last, and only then is the key genuinely `absent`.\n */\n const ANSWER_ORDER: readonly ReadStep<\n { readonly state: State; readonly key: string; readonly at: number },\n KeyAnswer<C>\n >[] = [\n {\n name: \"cache\",\n read: ({ state, key, at }) => {\n const answer = cacheGet(state.cache, key, at);\n return answer === undefined\n ? undefined\n : { status: \"answered\", answer };\n },\n },\n {\n name: \"failed\",\n read: ({ state, key }) => {\n const error = state.failed[key];\n return error === undefined ? undefined : { status: \"failed\", error };\n },\n },\n {\n name: \"inFlight\",\n read: ({ state, key }) =>\n keysInFlight(state).has(key) ? { status: \"pending\" } : undefined,\n },\n ];\n\n /**\n * What is known about `key` at `at`. PURE, derived — it stores nothing.\n *\n * `at` is a parameter rather than a clock read because a TTL answer is only\n * an answer relative to an instant, and this module reads no clock anywhere.\n * The host passes the `at` of the Msg it is handling.\n *\n * The precedence is {@link ANSWER_ORDER}, not this function's statement\n * order; `absent` is what every slice deferring means.\n */\n function answerFor(state: State, key: string, at: number): KeyAnswer<C> {\n return readInOrder({ state, key, at }, ANSWER_ORDER, { status: \"absent\" });\n }\n\n /**\n * The window's flush deadline while a window is open. Fan-out lists none (it\n * runs no timers). PURE.\n */\n function subs(\n state: State,\n id = \"jev-classify-batch\",\n ): readonly BatchWindowSub[] {\n return window.subs(state.window, id);\n }\n\n /**\n * The machine's `subs` entries, over a host Model that holds the slice where\n * `select` reads it: the window's built-in `timer`, plus the cache's eviction\n * tick when `evictEveryMs` is configured.\n */\n function subEntries<Model>(\n select: (model: Model) => State,\n id = \"jev-classify-batch\",\n ): readonly DepKeyedSub<\n Model,\n TimerSub<BatchWindowExpired> | CacheEvictionSub\n >[] {\n const flush: DepKeyedSub<Model, TimerSub<BatchWindowExpired>> = {\n type: \"timer\",\n deps: (model: Model) => window.timer(select(model).window, id),\n };\n if (config.evictEveryMs === undefined) return [flush];\n return [flush, cacheEvictionSub(id, config.evictEveryMs)];\n }\n\n /**\n * The runners the `subEntries` Subs need beyond the built-in `timer`, handed\n * to `run` as `subscribe`: the cache's eviction tick.\n */\n function subscribers() {\n return { cache: cacheEvictionSubscribe };\n }\n\n return {\n /** The ask Cmd def — list it in the machine's `cmds`. */\n ask: askCmd,\n init,\n add,\n onWindow,\n onBatchOk,\n onBatchErr,\n onEvict,\n answerFor,\n subs,\n subEntries,\n subscribers,\n /**\n * Your handler's outcome for Jev's reply to one batch — `../ask`'s\n * `decodeJevReply`, parsing against the questions the batch carried.\n */\n decode: (request: JevRequest<ItemQuestions<C>>, reply: JevHttpReply) =>\n decodeJevReply(request, reply),\n /** Your handler's outcome when the call threw — `../ask`'s `jevCallThrew`. */\n rejected: jevCallThrew,\n /** Your handler's outcome with no network — the configured `fallback`. */\n offline: (request: JevRequest<ItemQuestions<C>>) =>\n offlineJevAnswer(request, config.fallback),\n };\n}\n\n/** The bound knob {@link createClassifyBatch} returns. */\nexport type ClassifyBatchKnob<I, C extends string> = ReturnType<\n typeof createClassifyBatch<I, C>\n>;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/internal/jev/protocol/index.ts","../../src/internal/jev/ask/index.ts","../../src/internal/jev/classify-batch/index.ts"],"names":["at","fail"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * internal/jev/protocol — the wire contract of TypeSafe **Jev** (System One)\n * as types plus two pure functions, and nothing that performs I/O. Internal\n * since #216 — not published on any subpath; the `ask` child that opens the\n * socket imports it from here.\n *\n * The contract is transcribed from `https://docs.typesafe.ai/api`, re-verified\n * 2026-09-21: `POST https://api.typesafe.ai/v1/systemone`, `Authorization:\n * Bearer <API_KEY>`, request `{ state, model, questions }`, response\n * `{ model, answers, usage }`. Where this module and that page disagree, the\n * page is right and this module is a bug.\n *\n * ## What this module is for\n *\n * A Jev call is a map of questions you name, and an answer comes back under\n * each name. That is a shape TypeScript can hold onto, and the whole point of\n * typing it here is that a **choice** question's `criteria` keys ARE its\n * answer's `choice` domain — write the rubric once and the narrowing is free\n * at the call site:\n *\n * ```ts\n * const qs = jevQuestions({\n * category: {\n * type: \"choice\",\n * instructions: \"Which budget line is this?\",\n * criteria: { groceries: \"Supermarkets\", dining: \"Restaurants\" },\n * },\n * });\n * // parsed.answers.category.choice : \"groceries\" | \"dining\"\n * ```\n *\n * `jevQuestions` is an identity function with a `const` type parameter; it\n * exists so the literal keys survive inference without every call site\n * spelling `as const`. `JevAnswers<Q>` is the mapped type that turns such a\n * questions map into its answers map.\n *\n * ## Why parsing is a pure function returning data\n *\n * `parseAnswers` never throws — a response that disagrees with the questions\n * that produced it is a recoverable failure with an obvious next move (retry,\n * re-prompt, give up on this one item), so it is DATA: a closed `JevErr`\n * union of `{ _tag }` values that survives a JSON round-trip into Model\n * (ADR 0011). The type-level guarantee above is a *claim about the wire*, and\n * `parseAnswers` is the one place that claim is checked against a body the\n * network actually produced. Every `unknown` reaches a `JevErr`; nothing\n * reaches a `throw`.\n *\n * The check itself is a walk DERIVED from the questions map, one step per\n * field, and the typed value is the fresh object it builds. Both halves of a\n * choice question's claim are structural in that walk: `choice` must be one of\n * the criteria keys, and `probabilities` must carry exactly those keys — so a\n * distribution missing one or carrying an extra fails the check rather than a\n * separate arm a later reader has to remember. A partial distribution typed `Record<K, number>` is\n * the representable-invalid state this module exists to refuse, and it arrives\n * through this gate or not at all. The envelope is checked by the same schema:\n * `model` is a string and `usage` carries two finite numbers.\n *\n * The walk stops at the first field that fails, and that field's path is\n * mapped onto the `JevErr` arm that names it. It is hand-written rather than a\n * schema library's so the published package depends on none (ADR 0021 §5).\n *\n * ## Why status classification lives here and not in `ask`\n *\n * \"429 and 529 back off, everything else non-2xx is terminal\" is a fact about\n * the protocol, published in the same reference page as the body shapes, and\n * `ask` re-deriving it is how the retry policy and the wire contract drift\n * apart. `classifyStatus` is the single reading.\n *\n * ## Bounds the API enforces and this module does not\n *\n * A Choice takes at most 255 options and a Score takes at most 10 levels.\n * `JevScoreQuestion` encodes the *lower* bound — a Score with fewer than two\n * levels does not compile — because a two-element minimum is a tuple, which\n * costs nothing. The upper bounds are not encoded: spelling them as a union\n * of nine tuple arities (or 255 of them) buys a compile error in place of a\n * `422`, at the price of unreadable diagnostics on every ordinary Score. They\n * are the API's `422`, which `classifyStatus` already calls terminal.\n */\n\n// ── the shared instruction / description slot ──────────────────────────────\n\n/**\n * What every `instructions` and every criterion description accepts.\n *\n * The reference page allows a string, an object, or an array in all three\n * slots: a long question carrying data it refers to is written as an object\n * with the question in one field and the data in the others. Typing it as\n * `string` alone would reject the documented structured form.\n */\nexport type JevText =\n | string\n | Readonly<Record<string, unknown>>\n | readonly unknown[];\n\n// ── questions ──────────────────────────────────────────────────────────────\n\n/** The three question types, as the `type` discriminant spells them. */\nexport type JevQuestionType = \"choice\" | \"score\" | \"noul\";\n\n/**\n * A choice question's rubric: option key → description, or `null` where an\n * option needs no extra detail. The keys are the answer's `choice` domain.\n */\nexport type JevChoiceCriteria = Readonly<Record<string, JevText | null>>;\n\n/** Pick one option from a set you define. */\nexport interface JevChoiceQuestion<\n C extends JevChoiceCriteria = JevChoiceCriteria,\n> {\n readonly type: \"choice\";\n readonly instructions: JevText;\n readonly criteria: C;\n}\n\n/**\n * A score question's rubric: an ORDERED list of level descriptions, at least\n * two of them. The API accepts up to ten.\n */\nexport type JevScoreCriteria = readonly [JevText, JevText, ...JevText[]];\n\n/** Rate the state along an ordered rubric. */\nexport interface JevScoreQuestion {\n readonly type: \"score\";\n readonly instructions: JevText;\n readonly criteria: JevScoreCriteria;\n}\n\n/** A yes/no question. `criteria` is optional; both arms are required when it is present. */\nexport interface JevNoulQuestion {\n readonly type: \"noul\";\n readonly instructions: JevText;\n readonly criteria?: {\n readonly true: JevText;\n readonly false: JevText;\n };\n}\n\n/** One typed question. */\nexport type JevQuestion =\n | JevChoiceQuestion\n | JevScoreQuestion\n | JevNoulQuestion;\n\n/** The `questions` map: an id you choose → the question asked under it. */\nexport type JevQuestionMap = Readonly<Record<string, JevQuestion>>;\n\n/**\n * Identity, with the literal keys kept.\n *\n * The `const` type parameter is the whole function: it preserves each choice\n * question's `criteria` keys as literals, so `JevAnswers<typeof qs>` can name\n * them. Without it the inferred type is `Record<string, string>` and the\n * narrowing this module exists for is gone before `parseAnswers` is reached.\n */\nexport const jevQuestions = <const Q extends JevQuestionMap>(questions: Q): Q =>\n questions;\n\n// ── the request ────────────────────────────────────────────────────────────\n\n/** The evaluation endpoint. */\nexport const JEV_ENDPOINT = \"https://api.typesafe.ai/v1/systemone\" as const;\n\n/** The content to evaluate: text, or structured data. */\nexport type JevState =\n | string\n | Readonly<Record<string, unknown>>\n | readonly unknown[];\n\n/** The request body of `POST /v1/systemone`. */\nexport interface JevRequest<Q extends JevQuestionMap = JevQuestionMap> {\n readonly state: JevState;\n readonly model: string;\n readonly questions: Q;\n}\n\n// ── answers ────────────────────────────────────────────────────────────────\n\n/** The yes/no answer, on a scale from 0 (no) to 1 (yes). */\nexport interface JevNoulAnswer {\n readonly type: \"noul\";\n readonly noul: number;\n}\n\n/**\n * The chosen option and the full distribution over the options.\n *\n * `K` is the choice question's own criteria keys, which is why `choice` is a\n * union rather than a `string` and `probabilities` is total over the options.\n */\nexport interface JevChoiceAnswer<K extends string = string> {\n readonly type: \"choice\";\n readonly choice: K;\n readonly probabilities: Readonly<Record<K, number>>;\n readonly confidence: number;\n}\n\n/**\n * The probability-weighted value across the levels — it can land BETWEEN\n * levels, which is why `score` is a `number` and not an index. `legend` and\n * `probabilities` are keyed by the level index as a string.\n */\nexport interface JevScoreAnswer {\n readonly type: \"score\";\n readonly score: number;\n readonly legend: Readonly<Record<string, string>>;\n readonly probabilities: Readonly<Record<string, number>>;\n readonly confidence: number;\n}\n\n/** One typed answer. */\nexport type JevAnswer = JevChoiceAnswer | JevScoreAnswer | JevNoulAnswer;\n\n/** The answer a given question type yields, with the choice union carried through. */\nexport type JevAnswerFor<Q> = Q extends {\n readonly type: \"choice\";\n readonly criteria: infer C;\n}\n ? JevChoiceAnswer<Extract<keyof C, string>>\n : Q extends { readonly type: \"score\" }\n ? JevScoreAnswer\n : Q extends { readonly type: \"noul\" }\n ? JevNoulAnswer\n : never;\n\n/** A questions map turned into its answers map, id for id. */\nexport type JevAnswers<Q extends JevQuestionMap> = {\n readonly [K in keyof Q]: JevAnswerFor<Q[K]>;\n};\n\n/** Token usage for the request. */\nexport interface JevUsage {\n readonly input_tokens: number;\n readonly output_tokens: number;\n}\n\n/** The response body of `POST /v1/systemone`. */\nexport interface JevResponse<Q extends JevQuestionMap = JevQuestionMap> {\n readonly model: string;\n readonly answers: JevAnswers<Q>;\n readonly usage: JevUsage;\n}\n\n// ── failures are data ──────────────────────────────────────────────────────\n\n/**\n * Every way a response can fail to be the answers to the questions asked.\n *\n * Closed, `{ _tag }`-discriminated, JSON-round-trippable: a consumer folds one\n * of these into Model and routes on `_tag` (ADR 0011). `path` and `id` say\n * WHERE, so a failure names the question it is about rather than the whole\n * call.\n */\nexport type JevErr =\n /** The envelope is not a Jev response at all — not an object, or `model` /\n * `answers` / `usage` missing or of the wrong shape. */\n | { readonly _tag: \"malformed_body\"; readonly reason: string }\n /** A question id in the request has no answer under it in the response. */\n | { readonly _tag: \"missing_answer\"; readonly id: string }\n /** An answer arrived, but its `type` is not its question's. */\n | {\n readonly _tag: \"answer_type_mismatch\";\n readonly id: string;\n readonly expected: JevQuestionType;\n readonly received: string;\n }\n /** A choice answer picked something that is not one of its criteria keys. */\n | {\n readonly _tag: \"off_criteria_choice\";\n readonly id: string;\n readonly choice: string;\n readonly options: readonly string[];\n }\n /** A choice answer's `probabilities` keys are not exactly its question's\n * criteria keys. `JevChoiceAnswer<K>` types that map as TOTAL over `K`, so\n * a partial or off-criteria one is the representable-invalid state this\n * module exists to refuse. `missing` and `extra` say which way it failed. */\n | {\n readonly _tag: \"off_criteria_probabilities\";\n readonly id: string;\n readonly missing: readonly string[];\n readonly extra: readonly string[];\n readonly options: readonly string[];\n }\n /** The answer's `type` is right and its payload is not — a missing\n * `confidence`, a `probabilities` that is not a number map, and so on. */\n | {\n readonly _tag: \"malformed_answer\";\n readonly id: string;\n readonly reason: string;\n };\n\n/**\n * The tags {@link JevErr} is closed over, witnessed against the union.\n *\n * The `satisfies` is the point: it is the compiler checking this record's keys\n * against `JevErr[\"_tag\"]` in BOTH directions — a union arm with no entry here\n * fails totality, and an entry naming no arm fails the excess-property check\n * on the literal. Before it, the set was `ReadonlySet<string>` and a new arm\n * was tied to its runtime guard by reviewer attention alone: omit the entry\n * and {@link isJevErr} silently answers `false` for that arm forever.\n */\nconst JEV_ERR_TAG_WITNESS = {\n malformed_body: true,\n missing_answer: true,\n answer_type_mismatch: true,\n off_criteria_choice: true,\n off_criteria_probabilities: true,\n malformed_answer: true,\n} as const satisfies Record<JevErr[\"_tag\"], true>;\n\n/**\n * The membership set {@link isJevErr} tests against — the single reading of\n * the union's tags, so nothing downstream re-derives it from a `\"_tag\" in x`\n * test.\n */\nconst JEV_ERR_TAGS: ReadonlySet<JevErr[\"_tag\"]> = new Set(\n Object.keys(JEV_ERR_TAG_WITNESS) as readonly JevErr[\"_tag\"][],\n);\n\n/**\n * Is `value` a {@link JevErr}?\n *\n * The discriminant is the `_tag` VALUE against the closed set above, never the\n * bare presence of the key. A `JevAnswers` map is keyed by the caller's own\n * question ids, so a caller may legally name a question `_tag` — and then\n * `\"_tag\" in answers` is `true`. The value under it is an ANSWER, which is\n * always an object and never one of these string literals, so reading the\n * value is the one test a caller's id space cannot reach.\n */\nexport function isJevErr(value: object): value is JevErr {\n if (!(\"_tag\" in value)) return false;\n const tag: unknown = (value as { readonly _tag: unknown })._tag;\n // The set is keyed over the union's tags, and the question being asked is\n // whether an arbitrary string is one of them — a read-only widening, sound\n // because `has` only ever reads.\n const tags: ReadonlySet<string> = JEV_ERR_TAGS;\n return typeof tag === \"string\" && tags.has(tag);\n}\n\n/** What `parseAnswers` returns: typed answers, or one `JevErr`. Never a throw. */\nexport type JevParse<Q extends JevQuestionMap> =\n | {\n readonly ok: true;\n readonly answers: JevAnswers<Q>;\n readonly model: string;\n readonly usage: JevUsage;\n }\n | { readonly ok: false; readonly error: JevErr };\n\n// ── parsing ────────────────────────────────────────────────────────────────\n\ntype Obj = Readonly<Record<string, unknown>>;\n\nconst isObj = (v: unknown): v is Obj =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** The value under `key`, or `undefined` where there is no object to read. */\nconst at = (value: unknown, key: string): unknown =>\n isObj(value) ? value[key] : undefined;\n\nconst err = (\n error: JevErr,\n): { readonly ok: false; readonly error: JevErr } => ({\n ok: false,\n error,\n});\n\n// ── the check the questions derive ─────────────────────────────────\n\n/**\n * Where a body first disagrees with the questions: the path to the offending\n * value, `[]` for the body itself. The check stops at the first one.\n */\ntype Issue = { readonly path: readonly (string | number)[] };\n\n/** A step of the walk: the parsed value, or the first issue under `path`. */\ntype Step<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly issue: Issue };\n\nconst pass = <T>(value: T): Step<T> => ({ ok: true, value });\nconst fail = (path: readonly (string | number)[]): Step<never> => ({\n ok: false,\n issue: { path },\n});\n\n/** A finite number: the wire carries no `NaN` and no infinity. */\nconst isFiniteNumber = (v: unknown): v is number =>\n typeof v === \"number\" && Number.isFinite(v);\n\n/**\n * Check `value` field by field, in the order `fields` lists them, and build a\n * fresh object holding exactly those fields. Keys the list does not name are\n * dropped, never read.\n */\nconst object = (\n value: unknown,\n path: readonly (string | number)[],\n fields: ReadonlyArray<\n readonly [\n string,\n (v: unknown, p: readonly (string | number)[]) => Step<unknown>,\n ]\n >,\n): Step<Obj> => {\n if (!isObj(value)) return fail(path);\n const out: Record<string, unknown> = {};\n for (const [key, check] of fields) {\n const step = check(value[key], [...path, key]);\n if (!step.ok) return step;\n out[key] = step.value;\n }\n return pass(out);\n};\n\n/** A map whose every value passes `isValue`, copied. */\nconst record =\n (isValue: (v: unknown) => boolean) =>\n (value: unknown, path: readonly (string | number)[]): Step<Obj> => {\n if (!isObj(value)) return fail(path);\n for (const [key, v] of Object.entries(value)) {\n if (!isValue(v)) return fail([...path, key]);\n }\n return pass({ ...value });\n };\n\nconst literal =\n (expected: string) =>\n (value: unknown, path: readonly (string | number)[]): Step<string> =>\n value === expected ? pass(expected) : fail(path);\n\nconst finite = (\n value: unknown,\n path: readonly (string | number)[],\n): Step<number> => (isFiniteNumber(value) ? pass(value) : fail(path));\n\nconst string = (\n value: unknown,\n path: readonly (string | number)[],\n): Step<string> => (typeof value === \"string\" ? pass(value) : fail(path));\n\n/**\n * A choice's `probabilities`: TOTAL over the criteria keys and nothing else,\n * which is what `JevChoiceAnswer<K>` types it as. A key the body lacks fails\n * at that key (its value is not a number); a key the criteria do not name\n * fails at the map itself, after every named key has passed.\n */\nconst exactProbabilities =\n (options: readonly string[]) =>\n (value: unknown, path: readonly (string | number)[]): Step<Obj> => {\n const named = object(\n value,\n path,\n options.map((k) => [k, finite] as const),\n );\n if (!named.ok || !isObj(value)) return named;\n return Object.keys(value).every((k) => options.includes(k))\n ? named\n : fail(path);\n };\n\n/**\n * Check one answer against the question that asked for it.\n *\n * Field order is load-bearing — the walk stops at the FIRST failing field and\n * {@link toJevErr} maps that one, so this order is the precedence between two\n * arms that could both fire on one answer.\n *\n * That precedence reads the answer's own subject first: `choice`, the thing\n * the question asked for, then `confidence`, then the `probabilities`\n * distribution over it. A multi-fault body therefore names the semantically\n * interesting arm — an `off_criteria_choice` rather than a bad `confidence` —\n * which is the better report for a caller deciding whether to re-ask the\n * model. Inside `probabilities`, a bad or missing named key is found before an\n * extra one.\n *\n * The precedence is pinned by the \"multi-fault precedence\" cases in\n * `protocol.test.ts`, so a later reorder fails a test rather than this comment.\n */\nconst checkAnswer = (\n question: JevQuestion,\n value: unknown,\n path: readonly (string | number)[],\n): Step<Obj> => {\n switch (question.type) {\n case \"noul\":\n return object(value, path, [\n [\"type\", literal(\"noul\")],\n [\"noul\", finite],\n ]);\n case \"score\":\n return object(value, path, [\n [\"type\", literal(\"score\")],\n [\"confidence\", finite],\n [\"score\", finite],\n [\"legend\", record((v) => typeof v === \"string\")],\n [\"probabilities\", record(isFiniteNumber)],\n ]);\n case \"choice\": {\n const options = Object.keys(question.criteria);\n return object(value, path, [\n [\"type\", literal(\"choice\")],\n [\n \"choice\",\n (v, p) =>\n typeof v === \"string\" && options.includes(v) ? pass(v) : fail(p),\n ],\n [\"confidence\", finite],\n [\"probabilities\", exactProbabilities(options)],\n ]);\n }\n }\n};\n\n/**\n * Check the whole response body: the envelope, then the answers map keyed by\n * the ids the request asked about.\n *\n * Unknown ids are dropped rather than refused — the question-driven reading:\n * the questions are the contract, and a wider response still satisfies it.\n * Dropped means dropped: what comes back is a fresh object, not an alias of\n * the body's own `answers`, so a caller reaching past the type for an id it\n * never asked about no longer finds one.\n */\nconst checkResponse = (\n questions: JevQuestionMap,\n body: unknown,\n): Step<{\n readonly model: string;\n readonly usage: JevUsage;\n readonly answers: Obj;\n}> => {\n const envelope = object(\n body,\n [],\n [\n [\"model\", string],\n [\n \"usage\",\n (v, p) =>\n object(v, p, [\n [\"input_tokens\", finite],\n [\"output_tokens\", finite],\n ]),\n ],\n [\n \"answers\",\n (v, p) =>\n object(\n v,\n p,\n Object.entries(questions).map(\n ([id, question]) =>\n [id, (a, ap) => checkAnswer(question, a, ap)] as const,\n ),\n ),\n ],\n ],\n );\n if (!envelope.ok) return envelope;\n const { model, usage, answers } = envelope.value as {\n readonly model: string;\n readonly usage: JevUsage;\n readonly answers: Obj;\n };\n return pass({ model, usage, answers });\n};\n\n// ── the failure the check produces, as data ────────────────────────\n\n/** What a failed field of an answer says, where the path alone names it. */\nconst ANSWER_FIELD_REASON: Readonly<Record<string, string>> = {\n confidence: \"`confidence` is not a number\",\n score: \"`score` is not a number\",\n legend: \"`legend` is not a map of strings\",\n probabilities: \"`probabilities` is not a map of numbers\",\n choice: \"`choice` is not a string\",\n noul: \"`noul` is not a number\",\n};\n\n/**\n * Map the first issue onto the `JevErr` arm that names it.\n *\n * The issue's path says where, and `body` says what was actually there — which\n * is the difference between \"this answer picked an option nobody offered\" and\n * \"this answer's `choice` is not even a string\", two arms one failed `choice`\n * check covers.\n */\nconst toJevErr = (\n questions: JevQuestionMap,\n body: unknown,\n issue: Issue,\n): JevErr => {\n const [head, id, field] = issue.path;\n\n if (head === \"model\") {\n return { _tag: \"malformed_body\", reason: \"`model` is not a string\" };\n }\n if (head === \"usage\") {\n return {\n _tag: \"malformed_body\",\n reason: \"`usage` is not `{ input_tokens, output_tokens }`\",\n };\n }\n if (head !== \"answers\") {\n return { _tag: \"malformed_body\", reason: \"body is not an object\" };\n }\n\n const answers = at(body, \"answers\");\n if (!isObj(answers) || typeof id !== \"string\") {\n return { _tag: \"malformed_body\", reason: \"`answers` is not an object\" };\n }\n if (!Object.hasOwn(answers, id)) return { _tag: \"missing_answer\", id };\n\n const answer = answers[id];\n const question = questions[id];\n if (question === undefined || field === undefined) {\n return { _tag: \"malformed_answer\", id, reason: \"answer is not an object\" };\n }\n\n if (field === \"type\") {\n const received = at(answer, \"type\");\n return typeof received === \"string\"\n ? { _tag: \"answer_type_mismatch\", id, expected: question.type, received }\n : { _tag: \"malformed_answer\", id, reason: \"answer has no `type`\" };\n }\n\n if (question.type === \"choice\") {\n const options = Object.keys(question.criteria);\n\n if (field === \"choice\") {\n const choice = at(answer, \"choice\");\n if (typeof choice === \"string\") {\n return { _tag: \"off_criteria_choice\", id, choice, options };\n }\n }\n\n if (field === \"probabilities\") {\n const probabilities = at(answer, \"probabilities\");\n // A key the shape names and the body carries is a BAD VALUE; one the body\n // does not carry, or one the criteria do not name, is a totality failure.\n const key = issue.path[3];\n const badValue =\n isObj(probabilities) &&\n typeof key === \"string\" &&\n Object.hasOwn(probabilities, key);\n if (isObj(probabilities) && !badValue) {\n return {\n _tag: \"off_criteria_probabilities\",\n id,\n missing: options.filter((k) => !Object.hasOwn(probabilities, k)),\n extra: Object.keys(probabilities).filter((k) => !options.includes(k)),\n options,\n };\n }\n }\n }\n\n const named =\n typeof field === \"string\" ? ANSWER_FIELD_REASON[field] : undefined;\n return {\n _tag: \"malformed_answer\",\n id,\n reason: named ?? \"answer is malformed\",\n };\n};\n\n// ── parsing ──────────────────────────────────────────────\n\n/**\n * Turn an `unknown` response body into the typed answers for `questions`, or\n * into one `JevErr`. Pure, total, and never throwing on any input.\n *\n * The schema is question-driven: every id the request asked about must be\n * answered, each answer is checked against ITS question's type and — for a\n * choice — against that question's own criteria keys, both as the `choice`\n * picked and as the exact key set of `probabilities`. An answer under an id\n * nobody asked about is dropped rather than refused.\n */\nexport const parseAnswers = <Q extends JevQuestionMap>(\n questions: Q,\n body: unknown,\n): JevParse<Q> => {\n const parsed = checkResponse(questions, body);\n if (!parsed.ok) return err(toJevErr(questions, body, parsed.issue));\n const { answers, model, usage } = parsed.value;\n // The walk built `answers` id for id out of `questions`, each answer checked\n // against its own question; `Object.entries` lost the literal key types on\n // the way, and this names them back.\n return { ok: true, answers: answers as JevAnswers<Q>, model, usage };\n};\n\n// ── status classification ──────────────────────────────────────────────────\n\n/**\n * What a caller does next with an HTTP status.\n *\n * `retry` means back off and send it again; `terminal` means this request will\n * never succeed as written. `ok` is the 2xx arm — naming it keeps the function\n * total, so `ask` switches on three cases instead of testing 2xx itself and\n * then asking here.\n */\nexport type JevHttpStatus = \"ok\" | \"retry\" | \"terminal\";\n\n/**\n * Classify one HTTP status, per the reference page's error table.\n *\n * 429 (rate limited) and 529 (overloaded) are the two the page tells you to\n * back off on. 401 (bad key) and 422 (the body failed validation) will fail\n * identically forever. Every other non-2xx is terminal by default: an\n * unrecognized failure is not evidence that retrying helps, and a retry loop\n * over one is the expensive way to find that out.\n */\nexport const classifyStatus = (status: number): JevHttpStatus => {\n if (status >= 200 && status < 300) return \"ok\";\n if (status === 429 || status === 529) return \"retry\";\n return \"terminal\";\n};\n","/**\n * @packageDocumentation\n * internal/jev/ask — one Cmd that asks Jev a question map, over\n * `../../resilience/resilient-call`, with a pure fallback behind it. Internal\n * since #217 — not published on any subpath; `../protocol` is the wire contract\n * it speaks.\n *\n * This is `../../llm-call` for Jev: a config object, the resilient-call slice\n * it INHERITS, the `Cmd.define`d run Cmd, and the pure functions that turn an\n * HTTP reply into that Cmd's outcome. It ships no I/O (ADR 0021, 0022): the\n * HTTP call is the handler YOU write, in your engine's style, and it hands the\n * reply to `decode`. `init`, `attempt`, `succeed`, `onTimer`, `deadlines` and\n * `timer` are resilient-call's verbs forwarded; there is no retry loop and no\n * clock read in any verb here.\n *\n * ## The handler you write, and the one policy\n *\n * - The `resilient_run` handler calls Jev and returns\n * `ask.decode(cmd.input, { status, body })` — or `ask.rejected(cause)` when\n * the call itself threw. The key lives in your handler's closure, never in\n * config: this module never reads an env var or holds a key.\n * - `fallback?: (request) => JevAnswers<Q> | JevErr` — a PURE deterministic\n * decider. It answers on the two paths where the network cannot: a handler\n * with no key returns `ask.offline(cmd.input)`, and the resilient retry\n * budget is spent. Pure is the requirement, not a preference — the\n * exhaustion path calls it inside the `fail` verb, so a fallback that read\n * a clock or a socket would break replay.\n * - `retry: RetryPolicy` — handed straight to resilient-call. Omit it and the\n * first transient failure is terminal, exactly as resilient-call documents.\n *\n * ## Which failures back off and which do not\n *\n * `classifyStatus` (the protocol child, the single reading of the error table)\n * splits the answer three ways, and that split IS the retry decision:\n *\n * - `ok` → parse. A body that disagrees with the questions asked settles\n * `resilient_run_err` carrying the protocol's own {@link JevErr} —\n * terminal, because the same request will produce the same body.\n * - `retry` (429 / 529) → a TRANSIENT {@link JevAskErr}. `fail` feeds it to\n * resilient-call's backoff, so the call waits and re-issues.\n * - `terminal` (401 / 422 / anything else non-2xx) → a terminal\n * {@link JevAskErr}. `fail` settles it through `settleFailed`: the call\n * ends now, the breaker is not tripped and the handler is NOT run again.\n *\n * Every failure crosses the handler as the run Cmd's one declared tag,\n * `port_rejected`, with the typed {@link JevAskErr} riding on `jev`\n * ({@link JevRejected}). `fail` reads it back with {@link jevAskErrOf}.\n *\n * ## Typical wiring\n *\n * const ask = createJevAsk({\n * questions, // the map; its keys type the answers\n * fallback: (req) => offlineGuess(req),\n * retry: defaultRetryPolicy,\n * });\n *\n * // in the machine:\n * cmds: [ask.run],\n * init: () => [{ resilience: ask.init(), answers: null, failure: null }, []],\n * update: {\n * ask_jev: (s, m) => liftJevAsk(s, ask.attempt(s.resilience, m.key, m.state, m.at)),\n * resilient_run_ok: (s, m) => …ask.succeed(s.resilience, m)…,\n * resilient_run_err: (s, m) => …ask.fail(s.resilience, m)…,\n * deadline_exceeded: (s, m) => liftJevAsk(s, ask.onTimer(s.resilience, m)),\n * },\n * subs: [{ type: \"timer\", deps: (s) => ask.timer(s.resilience) }],\n *\n * // and where it runs — the HTTP call is your handler:\n * run(machine, {\n * interpret: {\n * resilient_run: async (cmd) => {\n * try { return ask.decode(cmd.input, await callJev(apiKey, cmd.input)); }\n * catch (cause) { return ask.rejected(cause); }\n * },\n * },\n * });\n *\n * ## Naming a knob, when a machine holds more than one\n *\n * Two unnamed knobs both speak `resilient_run` / `resilient_run_ok` /\n * `resilient_run_err`, so their handlers and settle cells collide. `name`\n * renames the whole family for one knob. It is resilient-call's own `name`,\n * forwarded:\n *\n * const judge = createJevAsk({ questions: judgeQuestions, name: \"judge\" });\n * const sort = createJevAsk({ questions: sortQuestions, name: \"sort\" });\n *\n * cmds: [judge.run, sort.run],\n * update: {\n * judge_run_ok: (s, m) => …, // m.value is JevOk<JudgeQuestions>\n * sort_run_ok: (s, m) => …, // m.value is JevOk<SortQuestions>\n * judge_deadline: (s, m) => …, // a named knob's timer Msg\n * },\n *\n * Both type parameters infer from the config. Omit `name` and every string\n * above is `resilient*`, with `deadline_exceeded` as the timer Msg.\n */\n\nimport { liftSlice } from \"../../../compose\";\nimport { describeError } from \"../../../describe-error\";\nimport { type Cmd, Outcome } from \"../../../index\";\nimport type { RetryPolicy } from \"../../../retry-backoff\";\nimport type { DeadlineSub } from \"../../resilience/deadline\";\nimport {\n createResilientCall,\n type DeadlineNameOf,\n type DefaultResilientName,\n type FailMsg,\n type ResilientConfig,\n type ResilientState,\n type ResilientTimerMsg,\n type RunCmd,\n runCmdDef,\n type SucceedMsg,\n} from \"../../resilience/resilient-call\";\nimport {\n classifyStatus,\n isJevErr,\n type JevAnswers,\n type JevErr,\n type JevQuestionMap,\n type JevRequest,\n type JevState,\n type JevUsage,\n parseAnswers,\n} from \"../protocol\";\n\n// ===========================================================================\n// The reply your handler hands back, and the fallback.\n// ===========================================================================\n\n/**\n * What an HTTP call to Jev came back with: the status and the undecoded body.\n *\n * Your handler returns this to {@link decodeJevReply} rather than throwing on a\n * non-2xx, because WHICH non-2xx it is decides whether the call backs off, and\n * a thrown error has already lost that.\n */\nexport interface JevHttpReply {\n readonly status: number;\n readonly body: unknown;\n}\n\n/**\n * The pure decider that answers when the network cannot.\n *\n * Called on exactly two paths: a handler with no key returns\n * {@link offlineJevAnswer}, and the resilient retry budget is spent. It must\n * be a pure function of the request — the exhaustion path runs it inside the\n * `fail` VERB, which replay re-runs, so a clock read or an I/O call here would\n * make the machine unreplayable. Returning a {@link JevErr} is how a fallback\n * says \"I have no answer for this one\"; the call then settles as that error.\n */\nexport type JevFallback<Q extends JevQuestionMap> = (\n request: JevRequest<Q>,\n) => JevAnswers<Q> | JevErr;\n\n// ===========================================================================\n// Failures — the protocol's, plus the three this module adds.\n// ===========================================================================\n\n/**\n * Every way an ask can fail, as DATA.\n *\n * A superset of the protocol child's {@link JevErr}: a parse failure is\n * reported with the protocol's own tag untouched, and the tags below are the\n * ones only a caller can observe. `{_tag}`-discriminated and\n * JSON-round-trippable like the rest (ADR 0011), because it is carried on the\n * settle Msg and stored in the slice's `failed` phase.\n *\n * `http_retry` and `port_threw` are the TRANSIENT pair — {@link isTransientJevAskErr}\n * is the single reading, and `fail` backs off on exactly those two.\n */\nexport type JevAskErr =\n | JevErr\n /** 429 / 529 — the statuses `classifyStatus` says to back off on. */\n | { readonly _tag: \"http_retry\"; readonly status: number }\n /** 401 / 422 / any other non-2xx — this request fails identically forever. */\n | { readonly _tag: \"http_terminal\"; readonly status: number }\n /** The call itself threw (a socket error, a DNS failure) — transient. */\n | { readonly _tag: \"port_threw\"; readonly reason: string }\n /** No key and no `fallback`: the knob was configured with no way to answer. */\n | { readonly _tag: \"no_answer_path\" };\n\n/**\n * Does this failure deserve another attempt?\n *\n * The one place the transient/terminal split is read. A rate limit and a dropped\n * socket are the two failures a second attempt can clear; a 401, a 422 and every\n * parse failure will reproduce exactly, so retrying one spends a real HTTP call\n * to learn nothing.\n */\nexport function isTransientJevAskErr(error: JevAskErr): boolean {\n return error._tag === \"http_retry\" || error._tag === \"port_threw\";\n}\n\n/**\n * How a {@link JevAskErr} crosses the handler: the run Cmd's declared\n * `port_rejected` tag, with the typed error on `jev`. The engine mints it into\n * `resilient_run_err` untouched, and {@link jevAskErrOf} reads it back.\n */\nexport type JevRejected = {\n readonly _tag: \"port_rejected\";\n readonly jev: JevAskErr;\n};\n\n/** Build the {@link JevRejected} outcome for one {@link JevAskErr}. PURE. */\nfunction reject(jev: JevAskErr): Outcome<never, JevRejected> {\n return Outcome.err({ _tag: \"port_rejected\", jev });\n}\n\n/**\n * Read a settled failure back as a {@link JevAskErr}. PURE.\n *\n * A {@link JevRejected} yields its `jev`. Anything else a handler returned —\n * a bare `port_rejected`, a `deadline_exceeded` — is a failure this module did\n * not classify, and it reads as the transient `port_threw`, exactly as a\n * foreign throw always has.\n */\nexport function jevAskErrOf(error: unknown): JevAskErr {\n if (typeof error === \"object\" && error !== null) {\n const jev = (error as { readonly jev?: unknown }).jev;\n if (\n typeof jev === \"object\" &&\n jev !== null &&\n typeof (jev as { readonly _tag?: unknown })._tag === \"string\"\n ) {\n return jev as JevAskErr;\n }\n }\n return { _tag: \"port_threw\", reason: describeError(error) };\n}\n\n// ===========================================================================\n// Config, request and result.\n// ===========================================================================\n\n/** The model the ask uses when config names none. */\nexport const DEFAULT_JEV_MODEL = \"jev-latest\";\n\n/** No tokens were spent — what a fallback answer's usage is. */\nconst NO_USAGE: JevUsage = { input_tokens: 0, output_tokens: 0 };\n\n/**\n * The jev-ask knob. `questions` is the load-bearing one: it is both the map\n * sent on every request and the type that makes the answers narrow at the call\n * site, so it belongs to the knob rather than to a call.\n */\nexport interface JevAskConfig<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> {\n /** The question map every request carries; its keys type the answers. */\n readonly questions: Q;\n /**\n * What this knob's Cmd and Msgs are called: `<name>_run`, `<name>_run_ok`,\n * `<name>_run_err` and the timer Msg `<name>_deadline`. Omit it and they are\n * `resilient_run`, `resilient_run_ok`, `resilient_run_err` and\n * `deadline_exceeded`. Name a knob when a machine holds more than one, so\n * each settles into its own `update` cell with its own answer type.\n */\n readonly name?: N;\n /** The pure decider for the no-key and budget-spent paths. Absent → those settle as an error. */\n readonly fallback?: JevFallback<Q>;\n /** Backoff policy, composed into `../../resilience/resilient-call`. Omit → no backoff. */\n readonly retry?: RetryPolicy;\n /** The model id asked for. Defaults to {@link DEFAULT_JEV_MODEL}. */\n readonly model?: string;\n}\n\n/**\n * The settled answer carried on `resilient_run_ok`.\n *\n * `source` is not decoration: a fallback answer and a model answer are both\n * valid answers with identical shape, and a host that routes on confidence — or\n * bills on usage — has to be able to tell them apart. A fallback's `usage` is\n * zero because nothing was spent.\n */\nexport interface JevOk<Q extends JevQuestionMap> {\n /** The typed answers, one per question id. */\n readonly answers: JevAnswers<Q>;\n /** The model that answered, as the response reported it (or the requested one, for a fallback). */\n readonly model: string;\n /** Tokens spent. Zero on the fallback path. */\n readonly usage: JevUsage;\n /** Which path produced this answer. */\n readonly source: \"port\" | \"fallback\";\n}\n\n// ===========================================================================\n// The pure outcome builders your handler returns.\n// ===========================================================================\n\n/**\n * A fallback's answer or its refusal.\n *\n * The discriminant is the protocol child's own {@link isJevErr} — the `_tag`\n * VALUE read against the closed `JevErr` tag set. Testing for the KEY instead\n * reads a caller's question ids as a discriminant they never agreed to: a\n * question legally named `_tag` makes `\"_tag\" in decided` true on a perfectly\n * good answers map, and the successful fallback then settles as a refusal\n * carrying the answers map as its error. An answer is always an object and\n * never one of those five tag literals, so the value test cannot collide.\n */\nfunction isErrDecision<Q extends JevQuestionMap>(\n decided: JevAnswers<Q> | JevErr,\n): decided is JevErr {\n return isJevErr(decided);\n}\n\n/**\n * Turn an HTTP reply into the run Cmd's outcome. PURE: classify the status,\n * then parse the body against the questions the request carried.\n *\n * `ok` → the typed {@link JevOk}; a 429 / 529 → the transient `http_retry`;\n * any other non-2xx → the terminal `http_terminal`; a body that disagrees with\n * the questions → the protocol's own {@link JevErr}. Every failure is a\n * {@link JevRejected}.\n */\nexport function decodeJevReply<Q extends JevQuestionMap>(\n request: JevRequest<Q>,\n reply: JevHttpReply,\n): Outcome<JevOk<Q>, JevRejected> {\n const kind = classifyStatus(reply.status);\n if (kind === \"retry\")\n return reject({ _tag: \"http_retry\", status: reply.status });\n if (kind === \"terminal\") {\n return reject({ _tag: \"http_terminal\", status: reply.status });\n }\n const parsed = parseAnswers(request.questions, reply.body);\n if (!parsed.ok) return reject(parsed.error);\n return Outcome.ok({\n answers: parsed.answers,\n model: parsed.model,\n usage: parsed.usage,\n source: \"port\",\n });\n}\n\n/**\n * The outcome for a call that threw before Jev answered — a socket error, a\n * DNS failure. PURE. It is the transient `port_threw`, so it backs off.\n */\nexport function jevCallThrew(cause: unknown): Outcome<never, JevRejected> {\n return reject({ _tag: \"port_threw\", reason: describeError(cause) });\n}\n\n/**\n * The outcome with no network at all: the fallback's answer, or its refusal,\n * or `no_answer_path` when there is no fallback. PURE — what a handler with\n * no key returns.\n */\nexport function offlineJevAnswer<Q extends JevQuestionMap>(\n request: JevRequest<Q>,\n fallback: JevFallback<Q> | undefined,\n): Outcome<JevOk<Q>, JevRejected> {\n if (fallback === undefined) return reject({ _tag: \"no_answer_path\" });\n const decided = fallback(request);\n if (isErrDecision(decided)) return reject(decided);\n return Outcome.ok({\n answers: decided,\n model: request.model,\n usage: NO_USAGE,\n source: \"fallback\",\n });\n}\n\n// ===========================================================================\n// The Cmd + Msgs the knob speaks.\n// ===========================================================================\n\n/**\n * The one effect this knob emits: run the ask for `key` with the plain-data\n * {@link JevRequest} (ADR 0014 — a Cmd is DECLARED, so its input, ok and err\n * channels are types on the constructor rather than a convention). It is\n * resilient-call's `runCmdDef` specialized to this module's input and result, the\n * same way `../../idempotency/idempotent-intake` mints its defs per knob: `Q` is\n * a type parameter, so the def is a factory and not a module constant. `name`\n * is the knob's name; omit it for the `resilient` family.\n */\nexport function jevAskCmdDef<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n>(name?: N) {\n return runCmdDef<JevRequest<Q>, JevOk<Q>, N>(name);\n}\n\n/** The Cmd the verbs emit — `<name>_run` carrying the request. */\nexport type JevAskCmd<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> = RunCmd<JevRequest<Q>, N, JevOk<Q>>;\n\n/** The success settle Msg — resilient-call's, with `value` narrowed to {@link JevOk}. */\nexport type JevSucceedMsg<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> = SucceedMsg<JevOk<Q>, N>;\n\n/**\n * The failure settle Msg the engine mints — resilient-call's. Its `error` is a\n * {@link JevRejected} when your handler used this module's builders; `fail`\n * reads it with {@link jevAskErrOf}.\n */\nexport type JevFailMsg<N extends string = DefaultResilientName> = FailMsg<N>;\n\n/**\n * The retry / deadline timer Msg — resilient-call's. `deadline_exceeded` for an\n * unnamed knob, `<name>_deadline` for a named one.\n */\nexport type JevTimerMsg<N extends string = DefaultResilientName> =\n ResilientTimerMsg<N>;\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a jev-ask knob from `config`. `rng` is injected for the inherited retry\n * jitter (pass a fixed `() => 0` in tests to pin backoff; defaults to\n * `Math.random`, read only at resilient-call's verb boundary).\n *\n * The slice and most verbs are resilient-call's, forwarded. `fail` is the one\n * composed verb — it reads the {@link JevAskErr} back off the settled Msg and\n * routes on it (see its docblock); it reimplements no backoff. `decode`,\n * `rejected` and `offline` are the pure outcome builders your handler returns.\n */\nexport function createJevAsk<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n>(config: JevAskConfig<Q, N>, rng: () => number = Math.random) {\n const resilientConfig: ResilientConfig<N> = {\n ...(config.name === undefined ? {} : { name: config.name }),\n ...(config.retry === undefined ? {} : { retry: config.retry }),\n };\n const rc = createResilientCall<JevRequest<Q>, JevOk<Q>, N>(\n resilientConfig,\n rng,\n );\n const model = config.model ?? DEFAULT_JEV_MODEL;\n\n /** The slice this knob owns — resilient-call's slice verbatim. */\n type State = ResilientState<JevRequest<Q>, JevOk<Q>>;\n\n /** The request a call under `key` is live with, or `undefined` if it is not live. */\n function liveRequest(s: State, key: string): JevRequest<Q> | undefined {\n const call = s.calls[key];\n if (call?.phase === \"running\" || call?.phase === \"waiting_retry\") {\n return call.input;\n }\n return undefined;\n }\n\n /** The starting slice — resilient-call's. */\n function init(): State {\n return rc.init();\n }\n\n /**\n * Ask the configured questions about `content` under `key`. PURE — it builds\n * the plain-data request (no closures: `state`, `model` and `questions` are\n * all values) and delegates straight to resilient-call's gate.\n */\n function attempt(\n s: State,\n key: string,\n content: JevState,\n at: number,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const request: JevRequest<Q> = {\n state: content,\n model,\n questions: config.questions,\n };\n return rc.attempt(s, key, request, at);\n }\n\n /** Record a settled answer. PURE — resilient-call's `settle`. */\n function succeed(\n s: State,\n msg: JevSucceedMsg<Q, N>,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const { call, cmds } = rc.settle(s, msg);\n return [call, cmds];\n }\n\n /**\n * Settle `key` from the fallback, as if the fallback's answer were the\n * response. PURE — the fallback is a pure decider, and `settle` /\n * `settleFailed` are resilient-call's own settle verbs. `s` is the state\n * BEFORE the failing attempt was recorded, so the call is still live and\n * `settle` closes it normally.\n */\n function settleFromFallback(\n s: State,\n key: string,\n request: JevRequest<Q>,\n fallback: JevFallback<Q>,\n at: number,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const decided = offlineJevAnswer(request, fallback);\n if (decided._tag === \"Err\") {\n return rc.settleFailed(s, key, decided.error.jev, at);\n }\n const { call, cmds } = rc.settle(s, {\n cmd: { key },\n value: decided.value,\n at,\n });\n return [call, cmds];\n }\n\n /**\n * Record a failure. PURE. The backoff itself is resilient-call's — nothing\n * here recomputes a delay, counts an attempt or reads a clock. What this verb\n * adds is the routing the typed error makes possible, and it is two\n * decisions:\n *\n * 1. A TERMINAL error settles through `settleFailed`, which ends the call\n * without touching the breaker or the retry counter. Handing a 401 or a\n * parse failure to `settle` instead would re-issue the identical request\n * and trip a breaker over a backend that is perfectly healthy.\n * 2. When the transient path EXHAUSTS the retry budget and a `fallback` is\n * configured, the fallback answers instead of the call settling failed.\n * This is the one place that can be observed — exhaustion is a fact of\n * the slice, not of the handler, so the handler cannot know it is on the\n * last attempt.\n *\n * The slice's `failed` phase stores the typed {@link JevAskErr}, never the\n * {@link JevRejected} carrier.\n */\n function fail(\n s: State,\n msg: JevFailMsg<N>,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n const { key } = msg.cmd;\n const error = jevAskErrOf(msg.error);\n if (!isTransientJevAskErr(error)) {\n return rc.settleFailed(s, key, error, msg.at);\n }\n const request = liveRequest(s, key);\n const { call, cmds, outcome } = rc.settle(s, { ...msg, error });\n if (\n outcome.kind !== \"failed\" ||\n config.fallback === undefined ||\n request === undefined\n ) {\n return [call, cmds];\n }\n return settleFromFallback(s, key, request, config.fallback, msg.at);\n }\n\n /** A retry / deadline timer fired. PURE — resilient-call's `onTimer`. */\n function onTimer(\n s: State,\n msg: JevTimerMsg<N>,\n ): readonly [State, readonly JevAskCmd<Q, N>[]] {\n return rc.onTimer(s, msg);\n }\n\n /** The call's deadlines — resilient-call's retry and deadline timers. */\n function deadlines(s: State): readonly DeadlineSub<DeadlineNameOf<N>>[] {\n return rc.deadlines(s);\n }\n\n return {\n name: rc.name,\n /** The `Cmd.define`d run Cmd — list it in the machine's `cmds`. */\n run: rc.run,\n init,\n attempt,\n succeed,\n fail,\n onTimer,\n deadlines,\n /** The built-in `timer` Sub's deps — resilient-call's `timer`. */\n timer: rc.timer,\n /** Your handler's outcome for an HTTP reply — {@link decodeJevReply}. */\n decode: (request: JevRequest<Q>, reply: JevHttpReply) =>\n decodeJevReply(request, reply),\n /** Your handler's outcome when the call threw — {@link jevCallThrew}. */\n rejected: jevCallThrew,\n /** Your handler's outcome with no network — {@link offlineJevAnswer}. */\n offline: (request: JevRequest<Q>) =>\n offlineJevAnswer(request, config.fallback),\n };\n}\n\n/**\n * The Cmd type a host machine declares in `types.cmd` when it splices a\n * {@link createJevAsk} knob in — {@link JevAskCmd} under the name a `types`\n * block reads well with. It is stated here so a host writes `cmd: {} as\n * JevCmd<Questions>` rather than deriving it from the knob's shape.\n */\nexport type JevCmd<\n Q extends JevQuestionMap,\n N extends string = DefaultResilientName,\n> = JevAskCmd<Q, N>;\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the slice\n * lives at `state.resilience` — resilient-call's convenience, re-typed for this\n * module's slice so a consumer wires one import. Pure.\n *\n * The record rebuild is `liftSlice` from the root entry (#231), the same helper\n * `liftResilience` is expressed over — this stays as the named, pre-keyed\n * convenience for this module, unchanged in name, signature and subpath.\n */\nexport function liftJevAsk<\n S extends { resilience: ResilientState<JevRequest<Q>, JevOk<Q>> },\n Q extends JevQuestionMap,\n C extends Cmd,\n>(\n state: S,\n result: readonly [ResilientState<JevRequest<Q>, JevOk<Q>>, readonly C[]],\n): readonly [S, readonly C[]] {\n return liftSlice(\"resilience\", state, result);\n}\n\nexport type { ResilientState };\n","/**\n * @packageDocumentation\n * internal/jev/classify-batch — the composition that turns a stream of items\n * into Jev calls. Internal since #218 — not published on any subpath.\n *\n * It writes NO chunker, NO cache, NO concurrency limiter and NO retry. Every\n * one of those already exists as a battery, and this module is the wiring:\n *\n * - `../../flow/batch-window` coalesces `add(item, at)` calls into size- or\n * time-bounded batches (`maxItems`, `maxMs`).\n * - `../../flow/fan-out` bounds how many batches are in flight\n * (`concurrency`), keyed by a batch id that is a pure function of the\n * batch's item keys.\n * - `../../resilience/cache` is the TTL cache keyed by `keyOf(item)`. An item\n * whose key is cached never enters a batch; every settled answer is written\n * back with `ttlMs`.\n * - `../ask` is the one effect: each flushed batch becomes one `resilient_run`\n * Cmd (`Cmd.define`d; its handler is yours, and hands the HTTP reply to\n * `decode`) carrying Jev's NATIVE batch form (grill #215, R1.2) — the item array\n * IS the request `state`, and `questions` holds one `choice` question per\n * item, keyed by `keyOf(item)`. There is no `{ transactions }` envelope of\n * any kind.\n *\n * ## The slice is plain data, and time arrives as `at`\n *\n * {@link ClassifyBatchState} is the three batteries' own slices plus a failure\n * map, so it survives a `Store` rehydrate unchanged. Every transition is a Msg\n * through the verbs (`add`, `onWindow`, `onBatchOk`, `onBatchErr`, `onEvict`);\n * no verb reads a clock — a settled batch carries the `at` the engine stamped.\n *\n * ## What this module deliberately does not decide\n *\n * `answerFor` is a derived READ. It reports `answered` / `failed` / `pending` /\n * `absent` and hands back the choice answer with its own `confidence`; whether\n * a given confidence auto-assigns or goes to triage is the HOST reducer's rule,\n * and putting a threshold here would make one caller's policy everybody's.\n *\n * ## Typical wiring\n *\n * const classify = createClassifyBatch({\n * keyOf: (t) => sanitizedMerchant(t),\n * criteria: { groceries: \"Supermarkets\", dining: \"Restaurants\" },\n * maxItems: 25,\n * maxMs: 2_000,\n * concurrency: 4,\n * ttlMs: 86_400_000,\n * });\n *\n * cmds: [classify.ask],\n * update: {\n * classify_add: (s, m) => lift(s, classify.add(s.classify, m.item, m.at)),\n * deadline_exceeded: (s, m) => lift(s, classify.onWindow(s.classify, m.atMs)),\n * resilient_run_ok: (s, m) => lift(s, classify.onBatchOk(s.classify, m)),\n * resilient_run_err: (s, m) => lift(s, classify.onBatchErr(s.classify, m)),\n * cache_evict: (s, m) => lift(s, classify.onEvict(s.classify, nowFromMsg)),\n * },\n * subs: classify.subEntries((s: Model) => s.classify),\n *\n * // and where it runs — the HTTP call is your handler:\n * run(machine, {\n * interpret: {\n * resilient_run: async (cmd) => {\n * try { return classify.decode(cmd.input, await callJev(apiKey, cmd.input)); }\n * catch (cause) { return classify.rejected(cause); }\n * },\n * },\n * subscribe: classify.subscribers(),\n * });\n */\n\nimport { liftSlice, type ReadStep, readInOrder } from \"../../../compose\";\nimport type { Cmd, DepKeyedSub, TimerSub } from \"../../../index\";\nimport {\n type BatchWindow,\n type BatchWindowExpired,\n type BatchWindowSub,\n createBatchWindow,\n} from \"../../flow/batch-window\";\nimport { createFanOut, type FanOutState } from \"../../flow/fan-out\";\nimport {\n type CacheEvictionSub,\n cacheEvictionSub,\n cacheEvictionSubscribe,\n get as cacheGet,\n has as cacheHas,\n set as cacheSet,\n evictExpired,\n initCache,\n type TtlCache,\n} from \"../../resilience/cache\";\nimport {\n DEFAULT_JEV_MODEL,\n decodeJevReply,\n type JevAskCmd,\n type JevAskErr,\n type JevFailMsg,\n type JevFallback,\n type JevHttpReply,\n type JevOk,\n type JevSucceedMsg,\n jevAskCmdDef,\n jevAskErrOf,\n jevCallThrew,\n offlineJevAnswer,\n} from \"../ask\";\nimport type {\n JevAnswerFor,\n JevChoiceQuestion,\n JevRequest,\n JevText,\n} from \"../protocol\";\n\n// ===========================================================================\n// The question shape one item gets.\n// ===========================================================================\n\n/**\n * The rubric every item is classified against: option key → description, or\n * `null` where an option needs no extra detail. `C` is the option union, and it\n * is what makes an answer's `choice` narrow at the call site.\n */\nexport type ClassifyCriteria<C extends string> = Readonly<\n Record<C, JevText | null>\n>;\n\n/** The one `choice` question an item is asked. */\nexport type ItemQuestion<C extends string> = JevChoiceQuestion<\n ClassifyCriteria<C>\n>;\n\n/**\n * The `questions` map of one batch: one {@link ItemQuestion} per item, keyed by\n * `keyOf(item)`. This IS Jev's native batch form — the questions map is the\n * fan-out over items, so nothing here wraps the items in an envelope of its own.\n */\nexport type ItemQuestions<C extends string> = Readonly<\n Record<string, ItemQuestion<C>>\n>;\n\n/** The answer one item's question yields. */\nexport type ItemAnswer<C extends string> = JevAnswerFor<ItemQuestion<C>>;\n\n// ===========================================================================\n// Batches, Cmds and Msgs.\n// ===========================================================================\n\n/**\n * One flushed batch, as plain data: the items and the id fan-out addresses it\n * by. `id` is a pure function of the batch's item keys (they are joined with a\n * separator no sanitized key contains), which is what lets a settle Msg — whose\n * `key` is that id — find its batch again after a rehydrate.\n */\nexport interface Batch<I> {\n readonly id: string;\n readonly items: readonly I[];\n}\n\n/** The Cmd a flushed batch becomes — `../ask`'s, carrying the native request. */\nexport type ClassifyBatchCmd<C extends string> = JevAskCmd<ItemQuestions<C>>;\n\n/** The success settle Msg — `../ask`'s, with the batch's answers. */\nexport type ClassifyBatchOkMsg<C extends string> = JevSucceedMsg<\n ItemQuestions<C>\n>;\n\n/** The failure settle Msg — `../ask`'s, with the typed {@link JevAskErr}. */\nexport type ClassifyBatchErrMsg = JevFailMsg;\n\n/**\n * The internal Cmd the batch window flushes into. It never leaves this module:\n * `add` and `onWindow` intercept it and hand the items to fan-out, which is\n * what bounds how many batches are in flight. Letting the window emit the ask\n * Cmd directly would flush straight past `concurrency`.\n */\ntype FlushCmd<I> = Cmd<\"classify_batch_flush\"> & {\n readonly items: readonly I[];\n};\n\n// ===========================================================================\n// Config and slice.\n// ===========================================================================\n\n/** The default page size — the one the grill settled on for Jev. */\nexport const DEFAULT_CLASSIFY_MAX_ITEMS = 25;\n\n/** The knob. Every field is a policy this module refuses to invent. */\nexport interface ClassifyBatchConfig<I, C extends string> {\n /**\n * The cache key and the question id of an item — e.g. a sanitized merchant\n * string, so two transactions at the same shop share one answer. PURE.\n */\n readonly keyOf: (item: I) => string;\n /** The rubric every item is classified against. Its keys are the answer domain. */\n readonly criteria: ClassifyCriteria<C>;\n /**\n * The `instructions` for one item's question. Defaults to a template naming\n * the item's key, which is what tells the model WHICH element of the request\n * `state` the question is about.\n */\n readonly instructions?: (key: string, item: I) => JevText;\n /** Size trigger, forwarded to the batch window. Defaults to {@link DEFAULT_CLASSIFY_MAX_ITEMS}. */\n readonly maxItems?: number;\n /** Latency ceiling in ms, forwarded to the batch window. */\n readonly maxMs: number;\n /** Max batches in flight, forwarded to fan-out. */\n readonly concurrency: number;\n /** How long a settled answer stays cached, forwarded to the TTL cache. */\n readonly ttlMs: number;\n /** Eviction tick for the TTL cache. Omit and no eviction Sub is declared. */\n readonly evictEveryMs?: number;\n /** The model asked for. Forwarded to `../ask`'s default when omitted. */\n readonly model?: string;\n /** The pure decider `offline` answers with, for a handler with no key. */\n readonly fallback?: JevFallback<ItemQuestions<C>>;\n}\n\n/**\n * The slice: the three batteries' own slices, plus the one fact none of them\n * holds — which keys a failed batch left unanswered. All plain data.\n */\nexport interface ClassifyBatchState<I, C extends string> {\n /** The open batch window — `../../flow/batch-window`'s slice. */\n readonly window: BatchWindow<I>;\n /** The in-flight batches — `../../flow/fan-out`'s slice. */\n readonly fanOut: FanOutState<Batch<I>, JevOk<ItemQuestions<C>>>;\n /** The answers, keyed by `keyOf` — `../../resilience/cache`'s slice. */\n readonly cache: TtlCache<ItemAnswer<C>>;\n /** Keys whose batch settled `resilient_run_err`, with the error that settled it. */\n readonly failed: Readonly<Record<string, JevAskErr>>;\n}\n\n/** What {@link ClassifyBatchKnob.answerFor} reports about one key. */\nexport type KeyAnswer<C extends string> =\n /** Answered and unexpired: the host applies its own confidence rule to this. */\n | { readonly status: \"answered\"; readonly answer: ItemAnswer<C> }\n /** The batch carrying this key settled `resilient_run_err`. Nothing was cached. */\n | { readonly status: \"failed\"; readonly error: JevAskErr }\n /** Buffered in the open window, or in a batch that has not settled. */\n | { readonly status: \"pending\" }\n /** Never seen, or cached and now expired. */\n | { readonly status: \"absent\" };\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a classify-batch knob from `config`.\n *\n * The verbs are pure and the whole composition is the four batteries'; the only\n * thing this factory adds is the native-batch request shape and the bookkeeping\n * that keeps one key out of two batches at once.\n */\nexport function createClassifyBatch<I, C extends string>(\n config: ClassifyBatchConfig<I, C>,\n) {\n type State = ClassifyBatchState<I, C>;\n type AskOk = JevOk<ItemQuestions<C>>;\n type Cmds = readonly ClassifyBatchCmd<C>[];\n\n const maxItems = config.maxItems ?? DEFAULT_CLASSIFY_MAX_ITEMS;\n const askCmd = jevAskCmdDef<ItemQuestions<C>>();\n const instructionsFor =\n config.instructions ??\n ((key: string): JevText =>\n `Classify the element of \\`state\\` whose key is \"${key}\".`);\n\n /** The batch's identity: its item keys, in arrival order. PURE. */\n function idOfBatch(batch: Batch<I>): string {\n return batch.id;\n }\n\n /** One batch's `questions` map — one choice question per item, keyed by `keyOf`. */\n function questionsFor(items: readonly I[]): ItemQuestions<C> {\n const questions: Record<string, ItemQuestion<C>> = {};\n for (const item of items) {\n const key = config.keyOf(item);\n questions[key] = {\n type: \"choice\",\n instructions: instructionsFor(key, item),\n criteria: config.criteria,\n };\n }\n return questions;\n }\n\n /**\n * The native Jev request for a batch: the item array IS the `state`, and the\n * questions map is the per-item fan-out. No envelope.\n */\n function requestFor(batch: Batch<I>): JevRequest<ItemQuestions<C>> {\n return {\n state: batch.items,\n model: config.model ?? DEFAULT_JEV_MODEL,\n questions: questionsFor(batch.items),\n };\n }\n\n const fanOut = createFanOut<\n Batch<I>,\n AskOk,\n ClassifyBatchCmd<C>,\n ClassifyBatchCmd<C>\n >({\n concurrency: config.concurrency,\n idOf: idOfBatch,\n of: (batch) => askCmd({ key: batch.id, input: requestFor(batch) }),\n });\n\n const window = createBatchWindow<I, FlushCmd<I>>({\n maxItems,\n maxMs: config.maxMs,\n flush: (items) => ({ type: \"classify_batch_flush\", items }),\n });\n\n /** The starting slice — each battery's own `init`, side by side. */\n function init(): State {\n return {\n window: window.init(),\n fanOut: fanOut.init(),\n cache: initCache<ItemAnswer<C>>(),\n failed: {},\n };\n }\n\n /** Every key currently buffered or in flight. PURE. */\n function keysInFlight(state: State): ReadonlySet<string> {\n const keys = new Set<string>();\n for (const item of state.window.buffer) keys.add(config.keyOf(item));\n for (const batch of state.fanOut.pending) {\n for (const item of batch.items) keys.add(config.keyOf(item));\n }\n for (const batch of state.fanOut.running) {\n for (const item of batch.items) keys.add(config.keyOf(item));\n }\n return keys;\n }\n\n /** The running batch a settle Msg's `key` addresses, or `undefined`. PURE. */\n function runningBatch(state: State, id: string): Batch<I> | undefined {\n return state.fanOut.running.find((batch) => batch.id === id);\n }\n\n /**\n * Hand a flushed window's items to fan-out. Fan-out returns the launch Cmds —\n * at most `concurrency` of them — so a window that flushes while the budget is\n * spent parks its batch in `pending` and launches nothing.\n */\n function scatter(\n state: State,\n flushed: readonly FlushCmd<I>[],\n ): readonly [State, Cmds] {\n if (flushed.length === 0) return [state, []];\n const batches = flushed.map((cmd) => ({\n id: cmd.items.map(config.keyOf).join(\"\\u0000\"),\n items: cmd.items,\n }));\n return liftSlice(\"fanOut\", state, fanOut.scatter(state.fanOut, batches));\n }\n\n /**\n * Offer `item` to the pipeline at `at`. PURE.\n *\n * An item whose key is cached, buffered, or already in a batch is DROPPED:\n * the cache hit is the whole point of the cache, and the other two are what\n * keeps one key out of two batches at once — which is the property that makes\n * a batch's id a usable identity and an answer's key unambiguous.\n *\n * An item that is NOT dropped loses its standing failure mark, exactly as\n * {@link onBatchOk} clears one: from the instant the key is buffered it is in\n * flight again, and a `failed` entry left standing would make `answerFor`\n * report the PREVIOUS attempt's error for the whole of the new one — telling a\n * host that polls it to stop waiting for work that is running.\n */\n function add(state: State, item: I, at: number): readonly [State, Cmds] {\n const key = config.keyOf(item);\n if (cacheHas(state.cache, key, at)) return [state, []];\n if (keysInFlight(state).has(key)) return [state, []];\n\n const failed: Record<string, JevAskErr> = { ...state.failed };\n delete failed[key];\n const [buffered, flushed] = liftSlice(\n \"window\",\n { ...state, failed },\n window.add(state.window, item, at),\n );\n return scatter(buffered, flushed);\n }\n\n /** The time window closed: flush whatever is buffered. PURE. */\n function onWindow(state: State, at: number): readonly [State, Cmds] {\n const [closed, flushed] = liftSlice(\n \"window\",\n state,\n window.onWindow(state.window, at),\n );\n return scatter(closed, flushed);\n }\n\n /**\n * A batch settled OK. PURE.\n *\n * Every answer is written back to the cache with `ttlMs` at `msg.at`, the\n * batch's keys lose any standing failure mark, and fan-out backfills the freed\n * concurrency slot — so the returned Cmds are the next batches' asks.\n *\n * An answer arrives under the question id it was asked under, which is the\n * item's own key, so the write-back needs no positional matching between the\n * request `state` array and the answers map.\n */\n function onBatchOk(\n state: State,\n msg: ClassifyBatchOkMsg<C>,\n ): readonly [State, Cmds] {\n let cache = state.cache;\n for (const [key, answer] of Object.entries(msg.value.answers)) {\n cache = cacheSet(cache, key, answer, msg.at, config.ttlMs);\n }\n const failed: Record<string, JevAskErr> = { ...state.failed };\n for (const key of Object.keys(msg.value.answers)) delete failed[key];\n\n return liftSlice(\n \"fanOut\",\n { ...state, cache, failed },\n fanOut.itemOk(state.fanOut, msg.cmd.key, msg.value),\n );\n }\n\n /**\n * A batch settled `resilient_run_err`. PURE.\n *\n * Each of its keys is marked failed with the error that settled it and the\n * cache is NOT touched: a failure is not an answer, and writing one back under\n * `ttlMs` would poison every later `add` for that key for the whole TTL. The\n * key stays re-addable, because it is neither cached nor in flight once\n * fan-out has moved the batch to `failed`.\n */\n function onBatchErr(\n state: State,\n msg: ClassifyBatchErrMsg,\n ): readonly [State, Cmds] {\n const batch = runningBatch(state, msg.cmd.key);\n const error = jevAskErrOf(msg.error);\n const failed: Record<string, JevAskErr> = { ...state.failed };\n if (batch !== undefined) {\n for (const item of batch.items) failed[config.keyOf(item)] = error;\n }\n return liftSlice(\n \"fanOut\",\n { ...state, failed },\n fanOut.itemErr(state.fanOut, msg.cmd.key, error),\n );\n }\n\n /** The eviction tick fired: drop every entry expired at `at`. PURE. */\n function onEvict(state: State, at: number): readonly [State, Cmds] {\n return liftSlice(\"cache\", state, [evictExpired(state.cache, at), []]);\n }\n\n /**\n * The precedence of {@link answerFor}'s composed read, as DATA — the order\n * the three battery slices are consulted in, cache first.\n *\n * Written as a value rather than as three `if` statements because this read\n * is where the composition's one escaped defect lived: a key whose batch\n * failed and which the host then re-added is IN FLIGHT, and reading `failed`\n * before the in-flight slices told a polling host to stop waiting for work\n * that was running (`bec6545`, criterion 7 of #214). As a value the order is\n * nameable and directly assertable — the per-battery suites cannot see it at\n * all.\n *\n * `cache` first: an unexpired answer is the strongest fact any slice holds.\n * `failed` next: a standing mark means the last attempt ended and none has\n * started since, because every path that starts one clears the mark. The\n * in-flight slices last, and only then is the key genuinely `absent`.\n */\n const ANSWER_ORDER: readonly ReadStep<\n { readonly state: State; readonly key: string; readonly at: number },\n KeyAnswer<C>\n >[] = [\n {\n name: \"cache\",\n read: ({ state, key, at }) => {\n const answer = cacheGet(state.cache, key, at);\n return answer === undefined\n ? undefined\n : { status: \"answered\", answer };\n },\n },\n {\n name: \"failed\",\n read: ({ state, key }) => {\n const error = state.failed[key];\n return error === undefined ? undefined : { status: \"failed\", error };\n },\n },\n {\n name: \"inFlight\",\n read: ({ state, key }) =>\n keysInFlight(state).has(key) ? { status: \"pending\" } : undefined,\n },\n ];\n\n /**\n * What is known about `key` at `at`. PURE, derived — it stores nothing.\n *\n * `at` is a parameter rather than a clock read because a TTL answer is only\n * an answer relative to an instant, and this module reads no clock anywhere.\n * The host passes the `at` of the Msg it is handling.\n *\n * The precedence is {@link ANSWER_ORDER}, not this function's statement\n * order; `absent` is what every slice deferring means.\n */\n function answerFor(state: State, key: string, at: number): KeyAnswer<C> {\n return readInOrder({ state, key, at }, ANSWER_ORDER, { status: \"absent\" });\n }\n\n /**\n * The window's flush deadline while a window is open. Fan-out lists none (it\n * runs no timers). PURE.\n */\n function subs(\n state: State,\n id = \"jev-classify-batch\",\n ): readonly BatchWindowSub[] {\n return window.subs(state.window, id);\n }\n\n /**\n * The machine's `subs` entries, over a host Model that holds the slice where\n * `select` reads it: the window's built-in `timer`, plus the cache's eviction\n * tick when `evictEveryMs` is configured.\n */\n function subEntries<Model>(\n select: (model: Model) => State,\n id = \"jev-classify-batch\",\n ): readonly DepKeyedSub<\n Model,\n TimerSub<BatchWindowExpired> | CacheEvictionSub\n >[] {\n const flush: DepKeyedSub<Model, TimerSub<BatchWindowExpired>> = {\n type: \"timer\",\n deps: (model: Model) => window.timer(select(model).window, id),\n };\n if (config.evictEveryMs === undefined) return [flush];\n return [flush, cacheEvictionSub(id, config.evictEveryMs)];\n }\n\n /**\n * The runners the `subEntries` Subs need beyond the built-in `timer`, handed\n * to `run` as `subscribe`: the cache's eviction tick.\n */\n function subscribers() {\n return { cache: cacheEvictionSubscribe };\n }\n\n return {\n /** The ask Cmd def — list it in the machine's `cmds`. */\n ask: askCmd,\n init,\n add,\n onWindow,\n onBatchOk,\n onBatchErr,\n onEvict,\n answerFor,\n subs,\n subEntries,\n subscribers,\n /**\n * Your handler's outcome for Jev's reply to one batch — `../ask`'s\n * `decodeJevReply`, parsing against the questions the batch carried.\n */\n decode: (request: JevRequest<ItemQuestions<C>>, reply: JevHttpReply) =>\n decodeJevReply(request, reply),\n /** Your handler's outcome when the call threw — `../ask`'s `jevCallThrew`. */\n rejected: jevCallThrew,\n /** Your handler's outcome with no network — the configured `fallback`. */\n offline: (request: JevRequest<ItemQuestions<C>>) =>\n offlineJevAnswer(request, config.fallback),\n };\n}\n\n/** The bound knob {@link createClassifyBatch} returns. */\nexport type ClassifyBatchKnob<I, C extends string> = ReturnType<\n typeof createClassifyBatch<I, C>\n>;\n"]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { C as Cmd, c as Sub, M as Machine } from './core-DNDAEhO2.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One unit in a fold trace — the four pieces of data a property
|
|
5
|
+
* invariant typically asserts on. `prev` is the state before the msg
|
|
6
|
+
* applied; `next` is the state after; `cmds` is the cmds emitted by this
|
|
7
|
+
* single step (NOT the running total).
|
|
8
|
+
*/
|
|
9
|
+
interface Step<S, M, C> {
|
|
10
|
+
readonly prev: S;
|
|
11
|
+
readonly msg: M;
|
|
12
|
+
readonly next: S;
|
|
13
|
+
readonly cmds: readonly C[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* What {@link foldEvents} hands back: every state in order, one {@link Step} per
|
|
17
|
+
* Msg, the final state, and the Cmds `init` sent. A law reads one of these.
|
|
18
|
+
*/
|
|
19
|
+
interface Fold<S, M, C> {
|
|
20
|
+
readonly states: readonly S[];
|
|
21
|
+
readonly steps: readonly Step<S, M, C>[];
|
|
22
|
+
readonly finalState: S;
|
|
23
|
+
readonly initCmds: readonly C[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fold a Msg sequence through a machine's pure reducer, returning the
|
|
27
|
+
* per-step trace, every state in order, the final state, and the Cmds `init`
|
|
28
|
+
* sent. Initial state comes from `replay(machine, { msgs: [], ctx, loaded })`
|
|
29
|
+
* — i.e. `machine.init(loaded ?? null, ctx)` faithfully. Init's emitted cmds
|
|
30
|
+
* are NOT included in any `Step` (they're the boot moment, not a Msg): they
|
|
31
|
+
* come back as `initCmds`, on a fresh start and on a loaded state alike.
|
|
32
|
+
*
|
|
33
|
+
* @param machine The machine under test.
|
|
34
|
+
* @param ctx The Ctx — typically `stubCtxThrowingProxy<Ctx>()` when
|
|
35
|
+
* init is pure. Pass a real ctx if init reads from it.
|
|
36
|
+
* @param loaded Optional pre-init snapshot (the `loaded` arg to
|
|
37
|
+
* `machine.init`). Defaults to `null` (fresh boot).
|
|
38
|
+
* @param msgs The Msg sequence to fold.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import type { Machine } from "@demlik/tea";
|
|
43
|
+
* import { foldEvents } from "@demlik/tea/pbt";
|
|
44
|
+
*
|
|
45
|
+
* type State = { type: "idle" } | { type: "counting"; n: number } | { type: "stopped" };
|
|
46
|
+
* type Msg = { type: "start" } | { type: "tick" } | { type: "stop" };
|
|
47
|
+
* interface Ctx { readonly tabId: number }
|
|
48
|
+
* declare const machine: Machine<State, Msg, never, never, Ctx>;
|
|
49
|
+
* declare const ctx: Ctx;
|
|
50
|
+
*
|
|
51
|
+
* const { states, steps, finalState } = foldEvents(machine, ctx, null, [
|
|
52
|
+
* { type: "start" }, { type: "tick" }, { type: "stop" },
|
|
53
|
+
* ]);
|
|
54
|
+
* // steps[1] = { prev: { type: "counting", n: 0 }, msg: { type: "tick" }, ... }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function foldEvents<S, M extends {
|
|
58
|
+
type: string;
|
|
59
|
+
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, loaded: NoInfer<S> | null, msgs: readonly NoInfer<M>[]): Fold<S, M, C>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What a broken law says. `report` names the fold that broke it. `reason` is the
|
|
63
|
+
* string the check returned to say why, or `null` when it returned `false`.
|
|
64
|
+
*
|
|
65
|
+
* @experimental
|
|
66
|
+
*/
|
|
67
|
+
interface LawBreak {
|
|
68
|
+
readonly report: string;
|
|
69
|
+
readonly reason: string | null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The law `propertyTerminates` runs: the fold's final state is one `terminal`
|
|
73
|
+
* accepts. `terminal` also gets the Cmds `init` sent.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import type { Machine } from "@demlik/tea";
|
|
78
|
+
* import { foldEvents, lawTerminates } from "@demlik/tea/pbt";
|
|
79
|
+
*
|
|
80
|
+
* type State = { type: "idle" } | { type: "done" };
|
|
81
|
+
* type Msg = { type: "finish" };
|
|
82
|
+
* declare const machine: Machine<State, Msg, never, never, object>;
|
|
83
|
+
*
|
|
84
|
+
* const fold = foldEvents(machine, {}, null, [{ type: "finish" }]);
|
|
85
|
+
* lawTerminates(fold, (s) => s.type === "done"); // null when it holds
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @experimental
|
|
89
|
+
*/
|
|
90
|
+
declare function lawTerminates<S, M, C>(fold: Fold<S, M, C>, terminal: (state: NoInfer<S>, initCmds: readonly NoInfer<C>[]) => boolean): LawBreak | null;
|
|
91
|
+
/**
|
|
92
|
+
* The law `propertyInvariant` runs: `invariant` holds on every step of the fold.
|
|
93
|
+
* Return `true` or `null` to accept, `false` or a string saying why to break. It
|
|
94
|
+
* stops at the first step that breaks, and the report names that step. The second
|
|
95
|
+
* argument is the Cmds `init` sent.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* import type { Machine } from "@demlik/tea";
|
|
100
|
+
* import { foldEvents, lawInvariant } from "@demlik/tea/pbt";
|
|
101
|
+
*
|
|
102
|
+
* type State = { readonly n: number };
|
|
103
|
+
* type Msg = { type: "inc" };
|
|
104
|
+
* declare const machine: Machine<State, Msg, never, never, object>;
|
|
105
|
+
*
|
|
106
|
+
* const fold = foldEvents(machine, {}, null, [{ type: "inc" }]);
|
|
107
|
+
* lawInvariant(fold, ({ prev, next }) => next.n > prev.n || "n did not grow");
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @experimental
|
|
111
|
+
*/
|
|
112
|
+
declare function lawInvariant<S, M, C>(fold: Fold<S, M, C>, invariant: (step: Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>, initCmds: readonly NoInfer<C>[]) => boolean | string | null): LawBreak | null;
|
|
113
|
+
/**
|
|
114
|
+
* The law `propertyTrace` runs: `predicate` holds on the fold's steps and final
|
|
115
|
+
* state together. Return `true` or `null` to accept, `false` or a string saying
|
|
116
|
+
* why to break. The last argument is the Cmds `init` sent.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* import type { Machine } from "@demlik/tea";
|
|
121
|
+
* import { foldEvents, lawTrace } from "@demlik/tea/pbt";
|
|
122
|
+
*
|
|
123
|
+
* type State = { type: "open" } | { type: "done" };
|
|
124
|
+
* type Msg = { type: "close" };
|
|
125
|
+
* declare const machine: Machine<State, Msg, never, never, object>;
|
|
126
|
+
*
|
|
127
|
+
* const fold = foldEvents(machine, {}, null, [{ type: "close" }]);
|
|
128
|
+
* lawTrace(fold, (steps, final) => final.type !== "done" || steps.length > 0);
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @experimental
|
|
132
|
+
*/
|
|
133
|
+
declare function lawTrace<S, M, C>(fold: Fold<S, M, C>, predicate: (steps: readonly Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>[], finalState: NoInfer<S>, initCmds: readonly NoInfer<C>[]) => boolean | string | null): LawBreak | null;
|
|
134
|
+
|
|
135
|
+
export { type Fold as F, type LawBreak as L, type Step as S, lawTerminates as a, lawTrace as b, foldEvents as f, lawInvariant as l };
|