@siftline/core 0.0.2 → 0.1.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 CHANGED
@@ -1,16 +1,43 @@
1
1
  # @siftline/core
2
2
 
3
- The Siftline engine: recipes, rules, fixtures and the judge wrapper.
3
+ The Siftline Engine: Recipes, the Judge, Rules, Fixtures and the Decision format.
4
4
 
5
- This package is a walking skeleton. It exports a placeholder constant, its own version
6
- and a placeholder fake client, which is enough to prove the build, type, test, pack and
7
- publish path. The Engine itself lands next.
5
+ ```sh
6
+ npm install @siftline/core @typesafe-ai/sdk
7
+ ```
8
8
 
9
9
  ```ts
10
- import { PLACEHOLDER, VERSION } from "@siftline/core";
11
- import { createFakeTypeSafeClient } from "@siftline/core/testing";
10
+ import { choice, createJudge, defineRecipe, noul, serializeDecision } from "@siftline/core";
11
+ import { TypeSafeClient } from "@typesafe-ai/sdk";
12
+
13
+ const recipe = defineRecipe({
14
+ name: "support-inbox",
15
+ version: 1,
16
+ model: "jev-1.13.0",
17
+ questions: {
18
+ category: choice("Which category best describes this message?", {
19
+ complaint: "The sender is unhappy with the product or service",
20
+ question: "The sender asks how something works",
21
+ other: "Anything else, including spam and thanks",
22
+ }),
23
+ wants_human: noul("Does the sender ask to speak to a person?"),
24
+ },
25
+ });
26
+
27
+ const client = new TypeSafeClient({ apiKey: process.env.TYPESAFE_API_KEY });
28
+ const judge = createJudge({ client, retry: "patient" });
29
+ const decision = await judge({ id: "msg-1", state: "…" }, recipe);
30
+
31
+ console.log(serializeDecision(decision));
12
32
  ```
13
33
 
34
+ `decision.answers.category` is `"complaint" | "question" | "other"`, not `string`: the
35
+ Recipe's labels travel through the types.
36
+
37
+ The guide and the full reference live at
38
+ [docs.siftline.dev](https://docs.siftline.dev/docs/packages/core). `@siftline/core/testing`
39
+ ships the scripted, replay and recording clients tests judge with.
40
+
14
41
  ESM only. Node 22.14 or newer.
15
42
 
16
43
  ## Licence
@@ -0,0 +1,162 @@
1
+ import { z } from "zod";
2
+ //#region src/recipe.d.ts
3
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
4
+ [key: string]: JsonValue;
5
+ };
6
+ /** The SDK's `EntryType`: prose, a JSON object or a JSON array. */
7
+ type EntryType = string | {
8
+ [key: string]: JsonValue;
9
+ } | JsonValue[] | null;
10
+ /** `instructions` is required on every Question, so the SDK's `null` is out. */
11
+ type Entry = Exclude<EntryType, null>;
12
+ type ChoiceCriteria = {
13
+ [label: string]: EntryType;
14
+ };
15
+ type ScoreCriteria = readonly [EntryType, EntryType, ...EntryType[]];
16
+ interface ChoiceQuestion<T extends ChoiceCriteria = ChoiceCriteria> {
17
+ type: "choice";
18
+ instructions: Entry;
19
+ criteria: T;
20
+ }
21
+ interface NoulQuestion {
22
+ type: "noul";
23
+ instructions: Entry;
24
+ criteria?: {
25
+ true?: EntryType;
26
+ false?: EntryType;
27
+ };
28
+ }
29
+ interface ScoreQuestion<T extends ScoreCriteria = ScoreCriteria> {
30
+ type: "score";
31
+ instructions: Entry;
32
+ criteria: T;
33
+ }
34
+ type Question = ChoiceQuestion | NoulQuestion | ScoreQuestion;
35
+ interface Questions {
36
+ [name: string]: Question;
37
+ }
38
+ declare function choice<const T extends ChoiceCriteria>(instructions: Entry, criteria: T): ChoiceQuestion<T>;
39
+ declare function noul(instructions: Entry, criteria?: NoulQuestion["criteria"]): NoulQuestion;
40
+ declare function score<const T extends ScoreCriteria>(instructions: Entry, criteria: T): ScoreQuestion<T>;
41
+ declare const questionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
42
+ type: z.ZodLiteral<"choice">;
43
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
44
+ criteria: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
45
+ }, z.core.$strict>, z.ZodObject<{
46
+ type: z.ZodLiteral<"noul">;
47
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
48
+ criteria: z.ZodOptional<z.ZodObject<{
49
+ true: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
50
+ false: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
51
+ }, z.core.$strict>>;
52
+ }, z.core.$strict>, z.ZodObject<{
53
+ type: z.ZodLiteral<"score">;
54
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
55
+ criteria: z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>], z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
56
+ }, z.core.$strict>], "type">;
57
+ declare const recipeSchema: z.ZodObject<{
58
+ format: z.ZodLiteral<1>;
59
+ name: z.ZodString;
60
+ version: z.ZodNumber;
61
+ model: z.ZodString;
62
+ reviewThreshold: z.ZodNumber;
63
+ questions: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
64
+ type: z.ZodLiteral<"choice">;
65
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
66
+ criteria: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
67
+ }, z.core.$strict>, z.ZodObject<{
68
+ type: z.ZodLiteral<"noul">;
69
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
70
+ criteria: z.ZodOptional<z.ZodObject<{
71
+ true: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
72
+ false: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
73
+ }, z.core.$strict>>;
74
+ }, z.core.$strict>, z.ZodObject<{
75
+ type: z.ZodLiteral<"score">;
76
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
77
+ criteria: z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>], z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
78
+ }, z.core.$strict>], "type">>;
79
+ }, z.core.$strict>;
80
+ /** `questions` is core's `Questions`, not `z.infer`: the parsed shape erases the literals. */
81
+ type Recipe<Q extends Questions = Questions> = Omit<z.infer<typeof recipeSchema>, "questions"> & {
82
+ questions: Q;
83
+ };
84
+ interface DefineRecipeInput<Q extends Questions> {
85
+ name: string;
86
+ version: number;
87
+ model: string;
88
+ reviewThreshold?: number;
89
+ questions: Q;
90
+ }
91
+ /** Returns the object it built, not `parse`'s copy, whose type is the erased `Recipe`. */
92
+ declare function defineRecipe<const Q extends Questions>(input: DefineRecipeInput<Q>): Recipe<Q>;
93
+ declare function parseRecipe(text: string): Recipe;
94
+ declare function serializeRecipe(recipe: Recipe): string;
95
+ //#endregion
96
+ //#region src/client.d.ts
97
+ /** A Choice answer as the wire returns it; `probabilities` key order is not the Recipe's. */
98
+ interface ChoiceResponse {
99
+ readonly type: "choice";
100
+ readonly choice: string;
101
+ readonly confidence: number;
102
+ readonly probabilities: {
103
+ readonly [label: string]: number;
104
+ };
105
+ }
106
+ /** A Noul answer. The wire carries no `confidence`; the Judge derives one. */
107
+ interface NoulResponse {
108
+ readonly type: "noul";
109
+ readonly noul: number;
110
+ }
111
+ interface ScoreResponse {
112
+ readonly type: "score";
113
+ /** Expected value across the rubric, not a level index. The answer is the argmax. */
114
+ readonly score: number;
115
+ readonly confidence: number;
116
+ readonly probabilities: {
117
+ readonly [index: string]: number;
118
+ };
119
+ /** Echoed criteria. Core never reads it, so its shape is the API's business. */
120
+ readonly legend?: unknown;
121
+ }
122
+ type AnswerResponse = ChoiceResponse | NoulResponse | ScoreResponse;
123
+ /** The wire shape, `usage` in snake case. The Judge camel-cases it into the Decision. */
124
+ interface SystemOneResult {
125
+ readonly model: string;
126
+ readonly answers: {
127
+ readonly [name: string]: AnswerResponse;
128
+ };
129
+ readonly usage: {
130
+ readonly input_tokens: number;
131
+ readonly output_tokens: number;
132
+ };
133
+ }
134
+ interface SystemOneRequest {
135
+ state: EntryType;
136
+ questions: Questions;
137
+ model: string;
138
+ }
139
+ /** The slice of the SDK's retry policy the Judge sets. The client owns the rest. */
140
+ interface RetryPolicy {
141
+ readonly maxRetries: number;
142
+ readonly backoffMaxMs: number;
143
+ readonly maxRetryAfterMs: number;
144
+ }
145
+ interface SystemOneCallOptions {
146
+ retry?: Partial<RetryPolicy>;
147
+ signal?: AbortSignal;
148
+ timeout?: number;
149
+ }
150
+ /**
151
+ * The only thing core asks of a client. Declared here, never imported from the SDK, so core
152
+ * publishes no dependency on it.
153
+ *
154
+ * `systemOne` is a function-typed property, not a method: under `strictFunctionTypes` that
155
+ * checks parameters contravariantly, so `types.test-d.ts` proving `TypeSafeClient` assignable
156
+ * to this interface proves something. A method signature would be bivariant.
157
+ */
158
+ interface SystemOneClient {
159
+ systemOne: (request: SystemOneRequest, options?: SystemOneCallOptions) => Promise<SystemOneResult>;
160
+ }
161
+ //#endregion
162
+ export { defineRecipe as C, recipeSchema as D, questionSchema as E, score as O, choice as S, parseRecipe as T, Question as _, ScoreResponse as a, ScoreCriteria as b, SystemOneRequest as c, ChoiceQuestion as d, DefineRecipeInput as f, NoulQuestion as g, JsonValue as h, RetryPolicy as i, serializeRecipe as k, SystemOneResult as l, EntryType as m, ChoiceResponse as n, SystemOneCallOptions as o, Entry as p, NoulResponse as r, SystemOneClient as s, AnswerResponse as t, ChoiceCriteria as u, Questions as v, noul as w, ScoreQuestion as x, Recipe as y };
package/dist/index.d.mts CHANGED
@@ -1,14 +1,341 @@
1
- //#region src/index.d.ts
1
+ import { C as defineRecipe, D as recipeSchema, E as questionSchema, O as score, S as choice, T as parseRecipe, _ as Question, a as ScoreResponse, b as ScoreCriteria, c as SystemOneRequest, d as ChoiceQuestion, f as DefineRecipeInput, g as NoulQuestion, h as JsonValue, i as RetryPolicy, k as serializeRecipe, l as SystemOneResult, m as EntryType, n as ChoiceResponse, o as SystemOneCallOptions, p as Entry, r as NoulResponse, s as SystemOneClient, t as AnswerResponse, u as ChoiceCriteria, v as Questions, w as noul, x as ScoreQuestion, y as Recipe } from "./client-B_yz9_JS.mjs";
2
+ import { z } from "zod";
3
+ //#region src/decision.d.ts
4
+ /** The Engine's whole view of an item. Sender, source and raw payload stay with the caller. */
5
+ interface Record {
6
+ id: string;
7
+ state: EntryType;
8
+ trimmed?: boolean;
9
+ }
10
+ /** Strict: an unknown key is a bad Record, not a field to ignore. */
11
+ export declare const recordSchema: z.ZodType<Record>;
12
+ type IndexOf<S extends readonly unknown[]> = number extends S["length"] ? number : Extract<keyof S, `${number}`> extends `${infer N extends number}` ? N : never;
13
+ type AnswerFor<Qn> = Qn extends {
14
+ type: "choice";
15
+ criteria: infer C;
16
+ } ? keyof C & string : Qn extends {
17
+ type: "noul";
18
+ } ? boolean : Qn extends {
19
+ type: "score";
20
+ criteria: infer S extends readonly unknown[];
21
+ } ? IndexOf<S> : never;
22
+ /** One answer with its literal erased: a label, a boolean or a level index. */
23
+ type AnswerValue = string | boolean | number;
24
+ /** Choice → label, Noul → boolean, Score → level index. Shared with Fixture `expect`. */
25
+ type Answers<Q extends Questions = Questions> = { [K in keyof Q]: AnswerFor<Q[K]>; };
26
+ type EvidenceFor<Qn> = Qn extends {
27
+ type: "choice";
28
+ criteria: infer C;
29
+ } ? {
30
+ confidence: number;
31
+ probabilities: { [L in keyof C]: number; };
32
+ } : Qn extends {
33
+ type: "noul";
34
+ } ? {
35
+ probability: number;
36
+ confidence: number;
37
+ } : Qn extends {
38
+ type: "score";
39
+ criteria: infer S extends readonly unknown[];
40
+ } ? {
41
+ score: number;
42
+ confidence: number;
43
+ probabilities: { [I in IndexOf<S>]: number; };
44
+ } : never;
45
+ /** The model's evidence for one Question. Bare `Evidence` is the union of the three shapes. */
46
+ type Evidence<Qn extends Question = Question> = EvidenceFor<Qn>;
47
+ interface Decision<Q extends Questions = Questions> {
48
+ format: 1;
49
+ id: string;
50
+ recordId: string;
51
+ recipe: {
52
+ name: string;
53
+ version: number;
54
+ };
55
+ model: string;
56
+ judgedAt: string;
57
+ trimmed: boolean;
58
+ answers: Answers<Q>;
59
+ questions: { [K in keyof Q]: Evidence<Q[K]>; };
60
+ confidence: number;
61
+ review: boolean;
62
+ rule: string | null;
63
+ action: string | null;
64
+ usage: {
65
+ inputTokens: number;
66
+ outputTokens: number;
67
+ };
68
+ }
69
+ export declare const decisionSchema: z.ZodObject<{
70
+ format: z.ZodLiteral<1>;
71
+ id: z.ZodString;
72
+ recordId: z.ZodString;
73
+ recipe: z.ZodObject<{
74
+ name: z.ZodString;
75
+ version: z.ZodNumber;
76
+ }, z.core.$strict>;
77
+ model: z.ZodString;
78
+ judgedAt: z.ZodISODateTime;
79
+ trimmed: z.ZodBoolean;
80
+ answers: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodBoolean, z.ZodNumber]>>;
81
+ questions: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
82
+ probability: z.ZodNumber;
83
+ confidence: z.ZodNumber;
84
+ }, z.core.$strict>, z.ZodObject<{
85
+ score: z.ZodNumber;
86
+ confidence: z.ZodNumber;
87
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
88
+ }, z.core.$strict>, z.ZodObject<{
89
+ confidence: z.ZodNumber;
90
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
91
+ }, z.core.$strict>]>>;
92
+ confidence: z.ZodNumber;
93
+ review: z.ZodBoolean;
94
+ rule: z.ZodNullable<z.ZodString>;
95
+ action: z.ZodNullable<z.ZodString>;
96
+ usage: z.ZodObject<{
97
+ inputTokens: z.ZodNumber;
98
+ outputTokens: z.ZodNumber;
99
+ }, z.core.$strict>;
100
+ }, z.core.$strict>;
101
+ export declare function parseDecision(line: string): Decision;
102
+ /** The only writer. One compact line, no trailing newline, and nothing is rounded. */
103
+ export declare function serializeDecision(decision: Decision): string;
104
+ //#endregion
105
+ //#region src/errors.d.ts
106
+ /** Cloud's error union, plus `action_build`, which only the toolkit raises. */
107
+ type SiftlineErrorCode = "jev_exhausted" | "jev_error" | "fixture_invalid" | "action_failed" | "action_build";
108
+ /** The base every toolkit error extends, in core and in `@siftline/actions`. */
109
+ export declare class SiftlineError extends Error {
110
+ readonly code: SiftlineErrorCode;
111
+ readonly retryable: boolean;
112
+ constructor(message: string, code: SiftlineErrorCode, retryable: boolean, options?: ErrorOptions);
113
+ }
114
+ //#endregion
115
+ //#region src/rules.d.ts
116
+ export declare const ruleConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
117
+ question: z.ZodString;
118
+ comparator: z.ZodLiteral<"is">;
119
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodBoolean, z.ZodNumber]>;
120
+ }, z.core.$strict>, z.ZodObject<{
121
+ question: z.ZodString;
122
+ comparator: z.ZodLiteral<"isOneOf">;
123
+ value: z.ZodArray<z.ZodString>;
124
+ }, z.core.$strict>, z.ZodObject<{
125
+ question: z.ZodString;
126
+ comparator: z.ZodLiteral<"atLeast">;
127
+ value: z.ZodNumber;
128
+ }, z.core.$strict>, z.ZodObject<{
129
+ question: z.ZodString;
130
+ comparator: z.ZodLiteral<"atMost">;
131
+ value: z.ZodNumber;
132
+ }, z.core.$strict>], "comparator">;
133
+ export declare const ruleSchema: z.ZodObject<{
134
+ id: z.ZodString;
135
+ condition: z.ZodDiscriminatedUnion<[z.ZodObject<{
136
+ question: z.ZodString;
137
+ comparator: z.ZodLiteral<"is">;
138
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodBoolean, z.ZodNumber]>;
139
+ }, z.core.$strict>, z.ZodObject<{
140
+ question: z.ZodString;
141
+ comparator: z.ZodLiteral<"isOneOf">;
142
+ value: z.ZodArray<z.ZodString>;
143
+ }, z.core.$strict>, z.ZodObject<{
144
+ question: z.ZodString;
145
+ comparator: z.ZodLiteral<"atLeast">;
146
+ value: z.ZodNumber;
147
+ }, z.core.$strict>, z.ZodObject<{
148
+ question: z.ZodString;
149
+ comparator: z.ZodLiteral<"atMost">;
150
+ value: z.ZodNumber;
151
+ }, z.core.$strict>], "comparator">;
152
+ action: z.ZodNullable<z.ZodString>;
153
+ }, z.core.$strict>;
154
+ type ConditionFor<K, Qn> = Qn extends {
155
+ type: "choice";
156
+ criteria: infer C;
157
+ } ? {
158
+ question: K;
159
+ comparator: "is";
160
+ value: keyof C & string;
161
+ } | {
162
+ question: K;
163
+ comparator: "isOneOf";
164
+ value: (keyof C & string)[];
165
+ } : Qn extends {
166
+ type: "noul";
167
+ } ? {
168
+ question: K;
169
+ comparator: "is";
170
+ value: boolean;
171
+ } : Qn extends {
172
+ type: "score";
173
+ criteria: infer S extends readonly unknown[];
174
+ } ? {
175
+ question: K;
176
+ comparator: "is" | "atLeast" | "atMost";
177
+ value: IndexOf<S>;
178
+ } : never;
179
+ type RuleCondition<Q extends Questions = Questions> = { [K in keyof Q & string]: ConditionFor<K, Q[K]>; }[keyof Q & string];
180
+ interface Rule<Q extends Questions = Questions> {
181
+ id: string;
182
+ condition: RuleCondition<Q>;
183
+ action: string | null;
184
+ }
185
+ /** What a Decision carries after routing: the Rule that matched and the Action it names. */
186
+ interface Routing {
187
+ rule: string | null;
188
+ action: string | null;
189
+ }
190
+ /** One stale Rule, keyed by its id. `validateRules` is the only producer. */
191
+ interface RuleProblem {
192
+ rule: string;
193
+ problem: string;
194
+ }
195
+ /** Pure, first match wins, no review gate. Cloud runs it again on corrected answers. */
196
+ export declare function evaluateRules(answers: Answers, rules: Rule[]): Routing;
2
197
  /**
3
- * The published version of `@siftline/core`, baked in at build time.
4
- *
5
- * Changesets bumps `package.json`; this constant follows it without a second edit.
198
+ * `NoInfer` keeps `Q` coming from the Decision alone, so an unannotated Rule literal is checked
199
+ * against it instead of widening it. Inside a generic body TS cannot see that `Rule<Q>` narrows
200
+ * `Rule`, so the public signature is an overload and the implementation is erased.
6
201
  */
7
- export declare const VERSION: string;
202
+ export declare function routeDecision<Q extends Questions>(decision: Decision<Q>, rules: NoInfer<Rule<Q>>[]): Decision<Q>;
203
+ /** The portal's stale-Rule warning. Never runs inside `evaluateRules`. */
204
+ export declare function validateRules(rules: Rule[], recipe: Recipe): RuleProblem[];
205
+ //#endregion
206
+ //#region src/judge.d.ts
207
+ /** The gate's width when the caller sets none (cloud ADR 0005). */
208
+ export declare const DEFAULT_MAX_IN_FLIGHT = 8;
209
+ /** `prompt` is the sync door, `patient` the batch one. The client does the retrying. */
210
+ type RetryMode = "prompt" | "patient";
211
+ type JudgeErrorReason = "max_tokens_exceeded" | "api_usage_error" | "network" | "timeout" | "invalid_answers" | "unknown";
212
+ /**
213
+ * The client gave up on a 429 or a 529. The fallback delay when `retryAfterMs` is `null`
214
+ * belongs to the caller.
215
+ */
216
+ export declare class JudgeExhaustedError extends SiftlineError {
217
+ readonly retryAfterMs: number | null;
218
+ constructor(message: string, retryAfterMs: number | null, options?: ErrorOptions);
219
+ }
220
+ /** Everything else a judged call can fail on. The thrown value stays on `cause`. */
221
+ export declare class JudgeError extends SiftlineError {
222
+ readonly status: number | null;
223
+ readonly reason: JudgeErrorReason;
224
+ constructor(message: string, reason: JudgeErrorReason, status: number | null, options?: ErrorOptions);
225
+ }
226
+ interface JudgeCallOptions {
227
+ /** Overrides the minted `crypto.randomUUID()`, which is what makes a replay byte-stable. */
228
+ id?: string;
229
+ signal?: AbortSignal;
230
+ }
231
+ interface Judge {
232
+ <Q extends Questions>(record: Record, recipe: Recipe<Q>, callOptions?: JudgeCallOptions): Promise<Decision<Q>>;
233
+ }
234
+ interface CreateJudgeOptions {
235
+ client: SystemOneClient;
236
+ retry: RetryMode;
237
+ maxInFlight?: number;
238
+ now?: () => Date;
239
+ }
240
+ export declare function createJudge(options: CreateJudgeOptions): Judge;
241
+ //#endregion
242
+ //#region src/fixtures.d.ts
243
+ export declare const fixtureSchema: z.ZodObject<{
244
+ id: z.ZodOptional<z.ZodString>;
245
+ origin: z.ZodOptional<z.ZodString>;
246
+ by: z.ZodOptional<z.ZodString>;
247
+ state: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodNull]>;
248
+ expect: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodBoolean, z.ZodNumber]>>;
249
+ }, z.core.$strict>;
250
+ /**
251
+ * One labelled Fixture. `expect` is `Answers<Q>` made partial, so it carries the Recipe's
252
+ * literal labels and level indices and needs no second mapping.
253
+ */
254
+ interface Fixture<Q extends Questions = Questions> {
255
+ state: EntryType;
256
+ expect: Partial<Answers<Q>>;
257
+ id?: string;
258
+ origin?: string;
259
+ by?: string;
260
+ }
261
+ /** A bad line, named by its 1-based position. Core never reads files, so there is no path. */
262
+ export declare class FixtureParseError extends SiftlineError {
263
+ readonly line: number;
264
+ constructor(message: string, line: number, options?: ErrorOptions);
265
+ }
266
+ export declare function parseFixture(line: string): Fixture;
267
+ /** The only writer. One compact line, no trailing newline, absent optionals omitted. */
268
+ export declare function serializeFixture(fixture: Fixture): string;
269
+ export declare function parseFixtures(text: string): Fixture[];
270
+ /** One Fixture that no longer fits its Recipe, keyed by its id or 1-based index. */
271
+ interface FixtureProblem {
272
+ fixture: string;
273
+ problem: string;
274
+ }
275
+ export declare class FixtureValidationError extends SiftlineError {
276
+ readonly problems: FixtureProblem[];
277
+ constructor(problems: FixtureProblem[], options?: ErrorOptions);
278
+ }
279
+ /** The portal's stale-Fixture warning, and what the runner refuses to start on. */
280
+ export declare function validateFixtures(fixtures: Fixture[], recipe: Recipe): FixtureProblem[];
8
281
  /**
9
- * Walking-skeleton export. It exists so the build, type, test, pack and publish path
10
- * can be proven before any Engine behaviour is written, and it will be deleted when
11
- * the first recipe lands.
282
+ * `NoInfer` keeps `Q` coming from the Recipe alone, so an unannotated Fixture literal is
283
+ * checked against it instead of widening it. The implementation is erased because inside a
284
+ * generic body TS cannot see that `Fixture<Q>` narrows `Fixture`.
12
285
  */
13
- export declare const PLACEHOLDER = "siftline-core-walking-skeleton";
14
- //#endregion
286
+ export declare function defineFixtures<Q extends Questions>(recipe: Recipe<Q>, fixtures: NoInfer<Fixture<Q>>[]): Fixture<Q>[];
287
+ /** `actual` is `undefined` when the Decision has no answer for the Question. */
288
+ interface Mismatch {
289
+ question: string;
290
+ expected: AnswerValue;
291
+ actual: AnswerValue | undefined;
292
+ }
293
+ /** Pure. Strict equality per type, no tolerance, in `expect` insertion order. */
294
+ export declare function compareAnswers(expect: Partial<Answers>, answers: Answers): Mismatch[];
295
+ interface FixtureResult {
296
+ index: number;
297
+ id: string;
298
+ decision: Decision;
299
+ expect: Partial<Answers>;
300
+ mismatches: Mismatch[];
301
+ review: boolean;
302
+ }
303
+ interface QuestionAccuracy {
304
+ asserted: number;
305
+ matched: number;
306
+ accuracy: number | null;
307
+ }
308
+ interface TestReport {
309
+ recipe: {
310
+ name: string;
311
+ version: number;
312
+ };
313
+ model: string;
314
+ fixtures: FixtureResult[];
315
+ questions: {
316
+ [question: string]: QuestionAccuracy;
317
+ };
318
+ accuracy: number | null;
319
+ }
320
+ /**
321
+ * The pure half. A Question absent from a Fixture's `expect` leaves that Question's
322
+ * denominator; an unsure Decision counts on its answers, because the threshold is a routing
323
+ * knob and must not hide errors.
324
+ */
325
+ export declare function scoreResults(results: FixtureResult[], recipe: Recipe): TestReport;
326
+ interface TestRecipeOptions {
327
+ signal?: AbortSignal;
328
+ onResult?: (result: FixtureResult) => void;
329
+ }
330
+ /**
331
+ * Fires every Fixture through the Judge at once and lets the Judge's gate meter concurrency.
332
+ * The first `JudgeError` or `JudgeExhaustedError` aborts the rest and is rethrown: a partial
333
+ * accuracy is worse than none.
334
+ */
335
+ export declare function testRecipe(judge: Judge, recipe: Recipe, fixtures: Fixture[], options?: TestRecipeOptions): Promise<TestReport>;
336
+ //#endregion
337
+ //#region src/index.d.ts
338
+ /** The published version of `@siftline/core`, baked in at build time. */
339
+ export declare const VERSION: string;
340
+ //#endregion
341
+ export { type AnswerResponse, type AnswerValue, type Answers, type ChoiceCriteria, type ChoiceQuestion, type ChoiceResponse, type CreateJudgeOptions, type Decision, type DefineRecipeInput, type Entry, type EntryType, type Evidence, type Fixture, type FixtureProblem, type FixtureResult, type JsonValue, type Judge, type JudgeCallOptions, type JudgeErrorReason, type Mismatch, type NoulQuestion, type NoulResponse, type Question, type QuestionAccuracy, type Questions, type Recipe, type Record, type RetryMode, type RetryPolicy, type Routing, type Rule, type RuleCondition, type RuleProblem, type ScoreCriteria, type ScoreQuestion, type ScoreResponse, type SiftlineErrorCode, type SystemOneCallOptions, type SystemOneClient, type SystemOneRequest, type SystemOneResult, type TestRecipeOptions, type TestReport, choice, defineRecipe, noul, parseRecipe, questionSchema, recipeSchema, score, serializeRecipe };