@smthrs/evals 0.0.0-stage → 1.0.0-rc.13

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.
Files changed (143) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -2
  4. package/dist/cjs/Baseline.d.ts +120 -0
  5. package/dist/cjs/Baseline.d.ts.map +1 -0
  6. package/dist/cjs/Baseline.js +160 -0
  7. package/dist/cjs/Baseline.js.map +7 -0
  8. package/dist/cjs/CaseExecutor.d.ts +85 -0
  9. package/dist/cjs/CaseExecutor.d.ts.map +1 -0
  10. package/dist/cjs/CaseExecutor.js +59 -0
  11. package/dist/cjs/CaseExecutor.js.map +7 -0
  12. package/dist/cjs/EvalError.d.ts +64 -0
  13. package/dist/cjs/EvalError.d.ts.map +1 -0
  14. package/dist/cjs/EvalError.js +54 -0
  15. package/dist/cjs/EvalError.js.map +7 -0
  16. package/dist/cjs/Gate.d.ts +55 -0
  17. package/dist/cjs/Gate.d.ts.map +1 -0
  18. package/dist/cjs/Gate.js +88 -0
  19. package/dist/cjs/Gate.js.map +7 -0
  20. package/dist/cjs/Regression.d.ts +116 -0
  21. package/dist/cjs/Regression.d.ts.map +1 -0
  22. package/dist/cjs/Regression.js +192 -0
  23. package/dist/cjs/Regression.js.map +7 -0
  24. package/dist/cjs/Report.d.ts +114 -0
  25. package/dist/cjs/Report.d.ts.map +1 -0
  26. package/dist/cjs/Report.js +161 -0
  27. package/dist/cjs/Report.js.map +7 -0
  28. package/dist/cjs/Runner.d.ts +229 -0
  29. package/dist/cjs/Runner.d.ts.map +1 -0
  30. package/dist/cjs/Runner.js +453 -0
  31. package/dist/cjs/Runner.js.map +7 -0
  32. package/dist/cjs/Suite.d.ts +139 -0
  33. package/dist/cjs/Suite.d.ts.map +1 -0
  34. package/dist/cjs/Suite.js +221 -0
  35. package/dist/cjs/Suite.js.map +7 -0
  36. package/dist/cjs/Trials.d.ts +93 -0
  37. package/dist/cjs/Trials.d.ts.map +1 -0
  38. package/dist/cjs/Trials.js +82 -0
  39. package/dist/cjs/Trials.js.map +7 -0
  40. package/dist/cjs/index.d.ts +76 -0
  41. package/dist/cjs/index.d.ts.map +1 -0
  42. package/dist/cjs/index.js +51 -0
  43. package/dist/cjs/index.js.map +7 -0
  44. package/dist/cjs/internal/CanonicalJson.d.ts +44 -0
  45. package/dist/cjs/internal/CanonicalJson.d.ts.map +1 -0
  46. package/dist/cjs/internal/CanonicalJson.js +114 -0
  47. package/dist/cjs/internal/CanonicalJson.js.map +7 -0
  48. package/dist/cjs/internal/controlCharacters.d.ts +44 -0
  49. package/dist/cjs/internal/controlCharacters.d.ts.map +1 -0
  50. package/dist/cjs/internal/controlCharacters.js +42 -0
  51. package/dist/cjs/internal/controlCharacters.js.map +7 -0
  52. package/dist/cjs/internal/scorerLabel.d.ts +33 -0
  53. package/dist/cjs/internal/scorerLabel.d.ts.map +1 -0
  54. package/dist/cjs/internal/scorerLabel.js +28 -0
  55. package/dist/cjs/internal/scorerLabel.js.map +7 -0
  56. package/dist/cjs/internal/tupleKey.d.ts +20 -0
  57. package/dist/cjs/internal/tupleKey.d.ts.map +1 -0
  58. package/dist/cjs/internal/tupleKey.js +25 -0
  59. package/dist/cjs/internal/tupleKey.js.map +7 -0
  60. package/dist/cjs/package.json +1 -0
  61. package/dist/esm/Baseline.d.ts +120 -0
  62. package/dist/esm/Baseline.d.ts.map +1 -0
  63. package/dist/esm/Baseline.js +208 -0
  64. package/dist/esm/Baseline.js.map +1 -0
  65. package/dist/esm/CaseExecutor.d.ts +85 -0
  66. package/dist/esm/CaseExecutor.d.ts.map +1 -0
  67. package/dist/esm/CaseExecutor.js +60 -0
  68. package/dist/esm/CaseExecutor.js.map +1 -0
  69. package/dist/esm/EvalError.d.ts +64 -0
  70. package/dist/esm/EvalError.d.ts.map +1 -0
  71. package/dist/esm/EvalError.js +65 -0
  72. package/dist/esm/EvalError.js.map +1 -0
  73. package/dist/esm/Gate.d.ts +55 -0
  74. package/dist/esm/Gate.d.ts.map +1 -0
  75. package/dist/esm/Gate.js +90 -0
  76. package/dist/esm/Gate.js.map +1 -0
  77. package/dist/esm/Regression.d.ts +116 -0
  78. package/dist/esm/Regression.d.ts.map +1 -0
  79. package/dist/esm/Regression.js +200 -0
  80. package/dist/esm/Regression.js.map +1 -0
  81. package/dist/esm/Report.d.ts +114 -0
  82. package/dist/esm/Report.d.ts.map +1 -0
  83. package/dist/esm/Report.js +188 -0
  84. package/dist/esm/Report.js.map +1 -0
  85. package/dist/esm/Runner.d.ts +229 -0
  86. package/dist/esm/Runner.d.ts.map +1 -0
  87. package/dist/esm/Runner.js +455 -0
  88. package/dist/esm/Runner.js.map +1 -0
  89. package/dist/esm/Suite.d.ts +139 -0
  90. package/dist/esm/Suite.d.ts.map +1 -0
  91. package/dist/esm/Suite.js +255 -0
  92. package/dist/esm/Suite.js.map +1 -0
  93. package/dist/esm/Trials.d.ts +93 -0
  94. package/dist/esm/Trials.d.ts.map +1 -0
  95. package/dist/esm/Trials.js +117 -0
  96. package/dist/esm/Trials.js.map +1 -0
  97. package/dist/esm/index.d.ts +76 -0
  98. package/dist/esm/index.d.ts.map +1 -0
  99. package/dist/esm/index.js +76 -0
  100. package/dist/esm/index.js.map +1 -0
  101. package/dist/esm/internal/CanonicalJson.d.ts +44 -0
  102. package/dist/esm/internal/CanonicalJson.d.ts.map +1 -0
  103. package/dist/esm/internal/CanonicalJson.js +202 -0
  104. package/dist/esm/internal/CanonicalJson.js.map +1 -0
  105. package/dist/esm/internal/controlCharacters.d.ts +44 -0
  106. package/dist/esm/internal/controlCharacters.d.ts.map +1 -0
  107. package/dist/esm/internal/controlCharacters.js +65 -0
  108. package/dist/esm/internal/controlCharacters.js.map +1 -0
  109. package/dist/esm/internal/scorerLabel.d.ts +33 -0
  110. package/dist/esm/internal/scorerLabel.d.ts.map +1 -0
  111. package/dist/esm/internal/scorerLabel.js +33 -0
  112. package/dist/esm/internal/scorerLabel.js.map +1 -0
  113. package/dist/esm/internal/tupleKey.d.ts +20 -0
  114. package/dist/esm/internal/tupleKey.d.ts.map +1 -0
  115. package/dist/esm/internal/tupleKey.js +20 -0
  116. package/dist/esm/internal/tupleKey.js.map +1 -0
  117. package/docs/README.md +128 -0
  118. package/docs/api.md +626 -0
  119. package/docs/concepts/determinism.md +64 -0
  120. package/docs/concepts/fixed-suites.md +71 -0
  121. package/docs/concepts/step-keys.md +60 -0
  122. package/docs/guides/author-a-suite.md +115 -0
  123. package/docs/guides/gate-a-run-in-ci.md +129 -0
  124. package/docs/guides/maintain-a-baseline.md +91 -0
  125. package/docs/guides/run-a-suite.md +150 -0
  126. package/docs/installation.md +32 -0
  127. package/docs/quickstart.md +238 -0
  128. package/docs/troubleshooting.md +153 -0
  129. package/package.json +198 -3
  130. package/src/Baseline.ts +266 -0
  131. package/src/CaseExecutor.ts +104 -0
  132. package/src/EvalError.ts +74 -0
  133. package/src/Gate.ts +128 -0
  134. package/src/Regression.ts +321 -0
  135. package/src/Report.ts +268 -0
  136. package/src/Runner.ts +789 -0
  137. package/src/Suite.ts +358 -0
  138. package/src/Trials.ts +155 -0
  139. package/src/index.ts +76 -0
  140. package/src/internal/CanonicalJson.ts +234 -0
  141. package/src/internal/controlCharacters.ts +66 -0
  142. package/src/internal/scorerLabel.ts +35 -0
  143. package/src/internal/tupleKey.ts +20 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Canonical JSON and Markdown regression reports.
3
+ *
4
+ * The JSON report is the machine-readable artifact; the Markdown report is what
5
+ * an operator reads in a CI log, so it names every case the run could not
6
+ * decide rather than only counting them.
7
+ *
8
+ * @since 0.1.0
9
+ */
10
+ import type { Baseline } from "./Baseline.ts";
11
+ import type { MissingObservation, Nondeterminism, Regression, Report as RegressionReport } from "./Regression.ts";
12
+ import type { CaseResult, RunResult } from "./Runner.ts";
13
+ /**
14
+ * Current serialized report format version.
15
+ *
16
+ * It versions {@link Data}, the wire shape, independently of the nested
17
+ * `Baseline.version`: the comparison can change shape without the committed
18
+ * baseline artifact changing at all.
19
+ *
20
+ * @category serialization
21
+ * @since 0.1.0
22
+ */
23
+ export declare const version: 1;
24
+ /** An index into {@link Data.run}'s `observations` table. */
25
+ type ObservationRef = number;
26
+ /**
27
+ * The serialized shape of a regression report.
28
+ *
29
+ * `run.observations` is the observation table: every observation the report
30
+ * mentions appears in it exactly once, and everything else refers to one by
31
+ * index. `run.cases[i].observations` is a list of indexes, and a regression or
32
+ * nondeterminism entry's `actual` is a single index. An in-memory
33
+ * `Regression.Report` carries the same observation object in up to four
34
+ * places, so encoding it directly repeated every reason and every `meta` that
35
+ * many times.
36
+ *
37
+ * `samples` and `inconclusive` are absent by construction: both are filters of
38
+ * `run.observations` by `kind`, so a reader recomputes them rather than
39
+ * downloading a third copy.
40
+ *
41
+ * @category serialization
42
+ * @since 0.1.0
43
+ */
44
+ export interface Data {
45
+ readonly reportVersion: typeof version;
46
+ readonly suite: string;
47
+ readonly baseline: Baseline;
48
+ readonly run: Omit<RunResult, "cases"> & {
49
+ readonly cases: ReadonlyArray<Omit<CaseResult, "observations"> & {
50
+ readonly observations: ReadonlyArray<ObservationRef>;
51
+ }>;
52
+ };
53
+ readonly regressions: ReadonlyArray<Omit<Regression, "actual"> & {
54
+ readonly actual: ObservationRef;
55
+ }>;
56
+ readonly nondeterminism: ReadonlyArray<Omit<Nondeterminism, "actual"> & {
57
+ readonly actual: ObservationRef;
58
+ }>;
59
+ readonly missing: ReadonlyArray<MissingObservation>;
60
+ }
61
+ /**
62
+ * Projects a comparison into its serialized form.
63
+ *
64
+ * The observation table starts as `run.observations` and grows to hold any
65
+ * observation reached only through a case or a comparison entry, so a
66
+ * hand-built report whose case lists are not flattened still serializes every
67
+ * reference. Observations are pooled by identity, not by value: two distinct
68
+ * objects that happen to be equal stay two rows, because collapsing them would
69
+ * claim one grading where there were two.
70
+ *
71
+ * @category serialization
72
+ * @since 0.1.0
73
+ */
74
+ export declare const data: (report: RegressionReport) => Data;
75
+ /**
76
+ * Serializes a regression report as stable, sorted-key JSON.
77
+ *
78
+ * The wire shape is {@link Data}, stamped with {@link version}: each
79
+ * observation is encoded once and referred to by index everywhere else. The
80
+ * report embeds each case's raw `execution.output`, which comes from an
81
+ * arbitrary target flow, so the encoding is total rather than trusting: keys
82
+ * are sorted by code unit, embedded strings are capped, total traversal is
83
+ * bounded, and anything JSON cannot express becomes a named marker
84
+ * (`[circular]`, `[NaN]`, `[function]`, `[budget exceeded]`) instead of a
85
+ * `RangeError` or a silent `null`. Two identical runs therefore produce
86
+ * byte-identical JSON.
87
+ *
88
+ * Nothing redacts the embedded output. A suite whose cases carry secrets must
89
+ * not print this report where the log is readable.
90
+ *
91
+ * @category serialization
92
+ * @since 0.1.0
93
+ */
94
+ export declare const json: (report: RegressionReport) => string;
95
+ /**
96
+ * Renders a concise stable Markdown regression report.
97
+ *
98
+ * Every count in the summary that is not zero has a section naming its rows:
99
+ * the regressions and the nondeterminism a gate reads as red, and the case
100
+ * failures, missing observations, and inconclusive observations that leave a
101
+ * gate undecided. A report that only counted them named nothing an operator
102
+ * could act on, which is exactly the run that needs debugging.
103
+ *
104
+ * Every cell and the suite heading value replace C0 controls and DEL with
105
+ * spaces and escape backslashes, pipes, inline GFM syntax, raw HTML, entities,
106
+ * and URL/email autolinks. Values are capped at 240 escaped UTF-16 code units,
107
+ * plus an ellipsis when truncated, without splitting escapes or code points.
108
+ *
109
+ * @category rendering
110
+ * @since 0.1.0
111
+ */
112
+ export declare const markdown: (report: RegressionReport) => string;
113
+ export {};
114
+ //# sourceMappingURL=Report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Report.d.ts","sourceRoot":"","sources":["../../src/Report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAI7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,IAAI,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACjH,OAAO,KAAK,EAAE,UAAU,EAAe,SAAS,EAAE,MAAM,aAAa,CAAA;AAErE;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,EAAG,CAAU,CAAA;AAEjC,6DAA6D;AAC7D,KAAK,cAAc,GAAG,MAAM,CAAA;AAE5B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,aAAa,EAAE,OAAO,OAAO,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,GAAG,EACR,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,GACxB;QACA,QAAQ,CAAC,KAAK,EAAE,aAAa,CACzB,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,GAChC;YAAE,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;SAAE,CAC3D,CAAA;KACF,CAAA;IACH,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;KAAE,CAAC,CAAA;IACrG,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;KAAE,CAAC,CAAA;IAC5G,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;CACpD;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,IAAI,GAAI,QAAQ,gBAAgB,KAAG,IAsB/C,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,IAAI,GAAI,QAAQ,gBAAgB,KAAG,MAC2C,CAAA;AAkD3F;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,gBAAgB,KAAG,MA4EnD,CAAA"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Canonical JSON and Markdown regression reports.
3
+ *
4
+ * The JSON report is the machine-readable artifact; the Markdown report is what
5
+ * an operator reads in a CI log, so it names every case the run could not
6
+ * decide rather than only counting them.
7
+ *
8
+ * @since 0.1.0
9
+ */
10
+ import { CanonicalJson } from "./internal/CanonicalJson.js";
11
+ import { isControlCodePoint } from "./internal/controlCharacters.js";
12
+ import { scorerLabel } from "./internal/scorerLabel.js";
13
+ /**
14
+ * Current serialized report format version.
15
+ *
16
+ * It versions {@link Data}, the wire shape, independently of the nested
17
+ * `Baseline.version`: the comparison can change shape without the committed
18
+ * baseline artifact changing at all.
19
+ *
20
+ * @category serialization
21
+ * @since 0.1.0
22
+ */
23
+ export const version = 1;
24
+ /**
25
+ * Projects a comparison into its serialized form.
26
+ *
27
+ * The observation table starts as `run.observations` and grows to hold any
28
+ * observation reached only through a case or a comparison entry, so a
29
+ * hand-built report whose case lists are not flattened still serializes every
30
+ * reference. Observations are pooled by identity, not by value: two distinct
31
+ * objects that happen to be equal stay two rows, because collapsing them would
32
+ * claim one grading where there were two.
33
+ *
34
+ * @category serialization
35
+ * @since 0.1.0
36
+ */
37
+ export const data = (report) => {
38
+ const positions = new Map();
39
+ const observations = [];
40
+ const refer = (observation) => {
41
+ const known = positions.get(observation);
42
+ if (known !== undefined)
43
+ return known;
44
+ const position = observations.length;
45
+ observations.push(observation);
46
+ positions.set(observation, position);
47
+ return position;
48
+ };
49
+ for (const observation of report.run.observations)
50
+ refer(observation);
51
+ const cases = report.run.cases.map((result) => ({ ...result, observations: result.observations.map(refer) }));
52
+ return {
53
+ reportVersion: version,
54
+ suite: report.suite,
55
+ baseline: report.baseline,
56
+ run: { ...report.run, cases, observations },
57
+ regressions: report.regressions.map((entry) => ({ ...entry, actual: refer(entry.actual) })),
58
+ nondeterminism: report.nondeterminism.map((entry) => ({ ...entry, actual: refer(entry.actual) })),
59
+ missing: report.missing
60
+ };
61
+ };
62
+ /**
63
+ * Serializes a regression report as stable, sorted-key JSON.
64
+ *
65
+ * The wire shape is {@link Data}, stamped with {@link version}: each
66
+ * observation is encoded once and referred to by index everywhere else. The
67
+ * report embeds each case's raw `execution.output`, which comes from an
68
+ * arbitrary target flow, so the encoding is total rather than trusting: keys
69
+ * are sorted by code unit, embedded strings are capped, total traversal is
70
+ * bounded, and anything JSON cannot express becomes a named marker
71
+ * (`[circular]`, `[NaN]`, `[function]`, `[budget exceeded]`) instead of a
72
+ * `RangeError` or a silent `null`. Two identical runs therefore produce
73
+ * byte-identical JSON.
74
+ *
75
+ * Nothing redacts the embedded output. A suite whose cases carry secrets must
76
+ * not print this report where the log is readable.
77
+ *
78
+ * @category serialization
79
+ * @since 0.1.0
80
+ */
81
+ export const json = (report) => CanonicalJson.stringify(data(report), { maxStringLength: CanonicalJson.maxStringLength });
82
+ /** Maximum escaped UTF-16 code units per value, before a truncation ellipsis. */
83
+ const maxCellLength = 240;
84
+ const cell = (value) => {
85
+ let text = "";
86
+ if (value !== undefined && value !== null) {
87
+ if (typeof value === "string")
88
+ text = value;
89
+ else {
90
+ try {
91
+ text = String(value);
92
+ }
93
+ catch {
94
+ text = "[unreadable]";
95
+ }
96
+ }
97
+ }
98
+ let escaped = "";
99
+ for (const character of text) {
100
+ const code = character.codePointAt(0);
101
+ // Escape each input character once, including backslashes before pipes.
102
+ // Ampersands prevent entities; dots, colons, and @ prevent GFM autolinks.
103
+ const token = isControlCodePoint(code)
104
+ ? " "
105
+ : /[\\|`*_[\]<>!#~&.:@]/u.test(character)
106
+ ? `\\${character}`
107
+ : character;
108
+ if (escaped.length + token.length > maxCellLength)
109
+ return `${escaped}…`;
110
+ escaped += token;
111
+ }
112
+ return escaped;
113
+ };
114
+ const scorerCell = (scorer, scorerName) => cell(scorerLabel({ scorer, scorerName }));
115
+ const section = (heading, header, alignment, rows) => rows.length === 0 ? [] : [
116
+ `## ${heading}`,
117
+ "",
118
+ `| ${header.join(" | ")} |`,
119
+ `| ${alignment.join(" | ")} |`,
120
+ ...rows.map((row) => `| ${row.join(" | ")} |`),
121
+ ""
122
+ ];
123
+ /**
124
+ * Renders a concise stable Markdown regression report.
125
+ *
126
+ * Every count in the summary that is not zero has a section naming its rows:
127
+ * the regressions and the nondeterminism a gate reads as red, and the case
128
+ * failures, missing observations, and inconclusive observations that leave a
129
+ * gate undecided. A report that only counted them named nothing an operator
130
+ * could act on, which is exactly the run that needs debugging.
131
+ *
132
+ * Every cell and the suite heading value replace C0 controls and DEL with
133
+ * spaces and escape backslashes, pipes, inline GFM syntax, raw HTML, entities,
134
+ * and URL/email autolinks. Values are capped at 240 escaped UTF-16 code units,
135
+ * plus an ellipsis when truncated, without splitting escapes or code points.
136
+ *
137
+ * @category rendering
138
+ * @since 0.1.0
139
+ */
140
+ export const markdown = (report) => {
141
+ const lines = [
142
+ `# Evaluation report: ${cell(report.suite)}`,
143
+ "",
144
+ `- Regressions: ${report.regressions.length}`,
145
+ `- Nondeterminism: ${report.nondeterminism.length}`,
146
+ `- Missing observations: ${report.missing.length}`,
147
+ `- Inconclusive observations: ${report.inconclusive.length}`,
148
+ `- Failed cases: ${report.run.cases.filter((result) => result.error !== undefined).length}`,
149
+ ...(report.run.trials === undefined ? [] : [
150
+ `- pass@1: ${report.run.trials.passAt1.toFixed(6)}`,
151
+ `- pass@${report.run.k ?? 1}: ${report.run.trials.passAtK.toFixed(6)}`,
152
+ `- pass^${report.run.k ?? 1}: ${report.run.trials.passHatK.toFixed(6)}`,
153
+ `- stderr: ${(Object.values(report.run.trials.perCase).reduce((sum, item) => sum + item.stderr, 0) / report.run.trials.cases)
154
+ .toFixed(6)}`
155
+ ]),
156
+ "",
157
+ ...section("Regressions", ["Case", "Scorer", "Baseline", "Actual", "Drop"], ["---", "---", "---:", "---:", "---:"], report.regressions.map((item) => [
158
+ cell(item.case),
159
+ scorerCell(item.scorer, item.actual.scorerName),
160
+ item.baseline.score.toFixed(6),
161
+ item.actual.score.toFixed(6),
162
+ item.drop.toFixed(6)
163
+ ])),
164
+ ...section("Nondeterminism", ["Case", "Scorer", "Step key", "Baseline", "Actual", "Delta"], ["---", "---", "---", "---:", "---:", "---:"], report.nondeterminism.map((item) => [
165
+ cell(item.case),
166
+ scorerCell(item.scorer, item.actual.scorerName),
167
+ cell(item.actual.stepKey),
168
+ item.baseline.score.toFixed(6),
169
+ item.actual.score.toFixed(6),
170
+ item.delta.toFixed(6)
171
+ ])),
172
+ ...section("Case failures", ["Case", "Code", "Message"], ["---", "---", "---"], report.run.cases.flatMap((result) => result.error === undefined ? [] : [[cell(result.case), cell(result.error.code), cell(result.error.message)]])),
173
+ ...section("Missing observations", ["Side", "Case", "Scorer", "Step key"], ["---", "---", "---", "---"], report.missing.map((item) => [
174
+ cell(item.side),
175
+ cell(item.case),
176
+ scorerCell(item.scorer, item.scorerName),
177
+ cell(item.stepKey)
178
+ ])),
179
+ ...section("Inconclusive", ["Case", "Scorer", "Step key", "Reason"], ["---", "---", "---", "---"], report.inconclusive.map((item) => [
180
+ cell(item.case),
181
+ scorerCell(item.scorer, item.scorerName),
182
+ cell(item.stepKey),
183
+ cell(item.reason)
184
+ ]))
185
+ ];
186
+ return `${lines.join("\n").trim()}\n`;
187
+ };
188
+ //# sourceMappingURL=Report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Report.js","sourceRoot":"","sources":["../../src/Report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Canonical JSON and Markdown regression reports.\n *\n * The JSON report is the machine-readable artifact; the Markdown report is what\n * an operator reads in a CI log, so it names every case the run could not\n * decide rather than only counting them.\n *\n * @since 0.1.0\n */\n\nimport type { Baseline } from \"./Baseline.ts\"\nimport { CanonicalJson } from \"./internal/CanonicalJson.ts\"\nimport { isControlCodePoint } from \"./internal/controlCharacters.ts\"\nimport { scorerLabel } from \"./internal/scorerLabel.ts\"\nimport type { MissingObservation, Nondeterminism, Regression, Report as RegressionReport } from \"./Regression.ts\"\nimport type { CaseResult, Observation, RunResult } from \"./Runner.ts\"\n\n/**\n * Current serialized report format version.\n *\n * It versions {@link Data}, the wire shape, independently of the nested\n * `Baseline.version`: the comparison can change shape without the committed\n * baseline artifact changing at all.\n *\n * @category serialization\n * @since 0.1.0\n */\nexport const version = 1 as const\n\n/** An index into {@link Data.run}'s `observations` table. */\ntype ObservationRef = number\n\n/**\n * The serialized shape of a regression report.\n *\n * `run.observations` is the observation table: every observation the report\n * mentions appears in it exactly once, and everything else refers to one by\n * index. `run.cases[i].observations` is a list of indexes, and a regression or\n * nondeterminism entry's `actual` is a single index. An in-memory\n * `Regression.Report` carries the same observation object in up to four\n * places, so encoding it directly repeated every reason and every `meta` that\n * many times.\n *\n * `samples` and `inconclusive` are absent by construction: both are filters of\n * `run.observations` by `kind`, so a reader recomputes them rather than\n * downloading a third copy.\n *\n * @category serialization\n * @since 0.1.0\n */\nexport interface Data {\n readonly reportVersion: typeof version\n readonly suite: string\n readonly baseline: Baseline\n readonly run:\n & Omit<RunResult, \"cases\">\n & {\n readonly cases: ReadonlyArray<\n & Omit<CaseResult, \"observations\">\n & { readonly observations: ReadonlyArray<ObservationRef> }\n >\n }\n readonly regressions: ReadonlyArray<Omit<Regression, \"actual\"> & { readonly actual: ObservationRef }>\n readonly nondeterminism: ReadonlyArray<Omit<Nondeterminism, \"actual\"> & { readonly actual: ObservationRef }>\n readonly missing: ReadonlyArray<MissingObservation>\n}\n\n/**\n * Projects a comparison into its serialized form.\n *\n * The observation table starts as `run.observations` and grows to hold any\n * observation reached only through a case or a comparison entry, so a\n * hand-built report whose case lists are not flattened still serializes every\n * reference. Observations are pooled by identity, not by value: two distinct\n * objects that happen to be equal stay two rows, because collapsing them would\n * claim one grading where there were two.\n *\n * @category serialization\n * @since 0.1.0\n */\nexport const data = (report: RegressionReport): Data => {\n const positions = new Map<Observation, ObservationRef>()\n const observations: Array<Observation> = []\n const refer = (observation: Observation): ObservationRef => {\n const known = positions.get(observation)\n if (known !== undefined) return known\n const position = observations.length\n observations.push(observation)\n positions.set(observation, position)\n return position\n }\n for (const observation of report.run.observations) refer(observation)\n const cases = report.run.cases.map((result) => ({ ...result, observations: result.observations.map(refer) }))\n return {\n reportVersion: version,\n suite: report.suite,\n baseline: report.baseline,\n run: { ...report.run, cases, observations },\n regressions: report.regressions.map((entry) => ({ ...entry, actual: refer(entry.actual) })),\n nondeterminism: report.nondeterminism.map((entry) => ({ ...entry, actual: refer(entry.actual) })),\n missing: report.missing\n }\n}\n\n/**\n * Serializes a regression report as stable, sorted-key JSON.\n *\n * The wire shape is {@link Data}, stamped with {@link version}: each\n * observation is encoded once and referred to by index everywhere else. The\n * report embeds each case's raw `execution.output`, which comes from an\n * arbitrary target flow, so the encoding is total rather than trusting: keys\n * are sorted by code unit, embedded strings are capped, total traversal is\n * bounded, and anything JSON cannot express becomes a named marker\n * (`[circular]`, `[NaN]`, `[function]`, `[budget exceeded]`) instead of a\n * `RangeError` or a silent `null`. Two identical runs therefore produce\n * byte-identical JSON.\n *\n * Nothing redacts the embedded output. A suite whose cases carry secrets must\n * not print this report where the log is readable.\n *\n * @category serialization\n * @since 0.1.0\n */\nexport const json = (report: RegressionReport): string =>\n CanonicalJson.stringify(data(report), { maxStringLength: CanonicalJson.maxStringLength })\n\n/** Maximum escaped UTF-16 code units per value, before a truncation ellipsis. */\nconst maxCellLength = 240\n\nconst cell = (value: unknown): string => {\n let text = \"\"\n if (value !== undefined && value !== null) {\n if (typeof value === \"string\") text = value\n else {\n try {\n text = String(value)\n } catch {\n text = \"[unreadable]\"\n }\n }\n }\n let escaped = \"\"\n for (const character of text) {\n const code = character.codePointAt(0)!\n // Escape each input character once, including backslashes before pipes.\n // Ampersands prevent entities; dots, colons, and @ prevent GFM autolinks.\n const token = isControlCodePoint(code)\n ? \" \"\n : /[\\\\|`*_[\\]<>!#~&.:@]/u.test(character)\n ? `\\\\${character}`\n : character\n if (escaped.length + token.length > maxCellLength) return `${escaped}…`\n escaped += token\n }\n return escaped\n}\n\nconst scorerCell = (scorer: string, scorerName: string | undefined): string => cell(scorerLabel({ scorer, scorerName }))\n\nconst section = (\n heading: string,\n header: ReadonlyArray<string>,\n alignment: ReadonlyArray<string>,\n rows: ReadonlyArray<ReadonlyArray<string>>\n): ReadonlyArray<string> =>\n rows.length === 0 ? [] : [\n `## ${heading}`,\n \"\",\n `| ${header.join(\" | \")} |`,\n `| ${alignment.join(\" | \")} |`,\n ...rows.map((row) => `| ${row.join(\" | \")} |`),\n \"\"\n ]\n\n/**\n * Renders a concise stable Markdown regression report.\n *\n * Every count in the summary that is not zero has a section naming its rows:\n * the regressions and the nondeterminism a gate reads as red, and the case\n * failures, missing observations, and inconclusive observations that leave a\n * gate undecided. A report that only counted them named nothing an operator\n * could act on, which is exactly the run that needs debugging.\n *\n * Every cell and the suite heading value replace C0 controls and DEL with\n * spaces and escape backslashes, pipes, inline GFM syntax, raw HTML, entities,\n * and URL/email autolinks. Values are capped at 240 escaped UTF-16 code units,\n * plus an ellipsis when truncated, without splitting escapes or code points.\n *\n * @category rendering\n * @since 0.1.0\n */\nexport const markdown = (report: RegressionReport): string => {\n const lines = [\n `# Evaluation report: ${cell(report.suite)}`,\n \"\",\n `- Regressions: ${report.regressions.length}`,\n `- Nondeterminism: ${report.nondeterminism.length}`,\n `- Missing observations: ${report.missing.length}`,\n `- Inconclusive observations: ${report.inconclusive.length}`,\n `- Failed cases: ${report.run.cases.filter((result) => result.error !== undefined).length}`,\n ...(report.run.trials === undefined ? [] : [\n `- pass@1: ${report.run.trials.passAt1.toFixed(6)}`,\n `- pass@${report.run.k ?? 1}: ${report.run.trials.passAtK.toFixed(6)}`,\n `- pass^${report.run.k ?? 1}: ${report.run.trials.passHatK.toFixed(6)}`,\n `- stderr: ${\n (Object.values(report.run.trials.perCase).reduce((sum, item) => sum + item.stderr, 0) / report.run.trials.cases)\n .toFixed(6)\n }`\n ]),\n \"\",\n ...section(\n \"Regressions\",\n [\"Case\", \"Scorer\", \"Baseline\", \"Actual\", \"Drop\"],\n [\"---\", \"---\", \"---:\", \"---:\", \"---:\"],\n report.regressions.map((item) => [\n cell(item.case),\n scorerCell(item.scorer, item.actual.scorerName),\n item.baseline.score.toFixed(6),\n item.actual.score.toFixed(6),\n item.drop.toFixed(6)\n ])\n ),\n ...section(\n \"Nondeterminism\",\n [\"Case\", \"Scorer\", \"Step key\", \"Baseline\", \"Actual\", \"Delta\"],\n [\"---\", \"---\", \"---\", \"---:\", \"---:\", \"---:\"],\n report.nondeterminism.map((item) => [\n cell(item.case),\n scorerCell(item.scorer, item.actual.scorerName),\n cell(item.actual.stepKey),\n item.baseline.score.toFixed(6),\n item.actual.score.toFixed(6),\n item.delta.toFixed(6)\n ])\n ),\n ...section(\n \"Case failures\",\n [\"Case\", \"Code\", \"Message\"],\n [\"---\", \"---\", \"---\"],\n report.run.cases.flatMap((result) =>\n result.error === undefined ? [] : [[cell(result.case), cell(result.error.code), cell(result.error.message)]]\n )\n ),\n ...section(\n \"Missing observations\",\n [\"Side\", \"Case\", \"Scorer\", \"Step key\"],\n [\"---\", \"---\", \"---\", \"---\"],\n report.missing.map((item) => [\n cell(item.side),\n cell(item.case),\n scorerCell(item.scorer, item.scorerName),\n cell(item.stepKey)\n ])\n ),\n ...section(\n \"Inconclusive\",\n [\"Case\", \"Scorer\", \"Step key\", \"Reason\"],\n [\"---\", \"---\", \"---\", \"---\"],\n report.inconclusive.map((item) => [\n cell(item.case),\n scorerCell(item.scorer, item.scorerName),\n cell(item.stepKey),\n cell(item.reason)\n ])\n )\n ]\n return `${lines.join(\"\\n\").trim()}\\n`\n}\n"]}
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Deterministic fixed-suite execution and bound scorer evaluation.
3
+ *
4
+ * A run executes every case through the injected {@link CaseExecutor}, then
5
+ * grades each execution with the scorers bound to the flow it executed. The
6
+ * timestamp and the run identity come from the caller, so two runs of the same
7
+ * suite over the same inputs produce byte-identical observations.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+ import * as ScorerRunner from "@smthrs/scorers/Runner";
12
+ import type * as ScoreStore from "@smthrs/scorers/ScoreStore";
13
+ import * as Context from "effect/Context";
14
+ import * as Effect from "effect/Effect";
15
+ import * as Layer from "effect/Layer";
16
+ import { CaseExecutor, type Execution } from "./CaseExecutor.ts";
17
+ import { EvalError } from "./EvalError.ts";
18
+ import type { Binding, Suite } from "./Suite.ts";
19
+ import * as Trials from "./Trials.ts";
20
+ /**
21
+ * One score observation emitted by a suite run.
22
+ *
23
+ * `scorer` is the scorer key, a digest of the scorer's own declaration, which
24
+ * is what a baseline matches on. `scorerName` is the human name from the same
25
+ * declaration, carried alongside so a report can be read without grepping for
26
+ * the digest. `at` is the run's timestamp, not the scorer's: a run's
27
+ * observations all carry one instant so a baseline stays reproducible.
28
+ *
29
+ * @category models
30
+ * @since 0.1.0
31
+ */
32
+ export type Observation = {
33
+ readonly case: string;
34
+ readonly scorer: string;
35
+ readonly scorerName?: string | undefined;
36
+ readonly stepKey: string;
37
+ readonly at: string;
38
+ } & ({
39
+ readonly kind: "score";
40
+ readonly score: number;
41
+ readonly reason?: string | undefined;
42
+ readonly meta?: unknown;
43
+ } | {
44
+ readonly kind: "inconclusive";
45
+ readonly reason: string;
46
+ readonly meta?: unknown;
47
+ });
48
+ /**
49
+ * A request sent to the scorers batch runner.
50
+ *
51
+ * @category models
52
+ * @since 0.1.0
53
+ */
54
+ export interface ScoreRequest {
55
+ readonly case: string;
56
+ readonly stepKey: string;
57
+ readonly binding: Binding;
58
+ readonly input: {
59
+ readonly input: unknown;
60
+ readonly output: unknown;
61
+ readonly groundTruth?: unknown;
62
+ readonly context?: unknown;
63
+ readonly latencyMs?: number;
64
+ };
65
+ }
66
+ /**
67
+ * A blocking scorer job, matching the `Runner` module of `@smthrs/scorers`.
68
+ *
69
+ * @category models
70
+ * @since 0.1.0
71
+ */
72
+ export type ScoreJob = ScorerRunner.Job;
73
+ /**
74
+ * Structural adapter for `@smthrs/scorers`' blocking batch runner.
75
+ *
76
+ * A runner that implements `runBatchCorrelated` tags every observation with
77
+ * its job identity and may return results in any order. A runner that implements
78
+ * only `runBatch` is correlated positionally: it must return exactly one
79
+ * observation per job, in order, and each observation must echo its job's
80
+ * `targetStepKey` and `scorerKey`. A run refuses the order-only protocol before
81
+ * scoring when two jobs share that pair, because their results could not be
82
+ * attributed to a case.
83
+ *
84
+ * A run fails with `scorer_protocol` when either protocol is broken.
85
+ * `@smthrs/scorers`' `Runner.Service` implements `runBatchCorrelated`, so its
86
+ * service value can be used directly and `ambiguous_score_job` never applies
87
+ * to it.
88
+ *
89
+ * @category services
90
+ * @since 0.1.0
91
+ */
92
+ export interface ScoreBatchRunner {
93
+ readonly runBatch: (jobs: ReadonlyArray<ScoreJob>, options?: {
94
+ readonly concurrency?: number | undefined;
95
+ }) => Effect.Effect<ReadonlyArray<ScoreObservation>, unknown>;
96
+ readonly runBatchCorrelated?: ((jobs: ReadonlyArray<ScoreJob>, options?: {
97
+ readonly concurrency?: number | undefined;
98
+ }) => Effect.Effect<ReadonlyArray<BatchResult>, unknown>) | undefined;
99
+ }
100
+ /**
101
+ * A score result aligned with a {@link ScoreRequest}.
102
+ *
103
+ * @category models
104
+ * @since 0.1.0
105
+ */
106
+ export type ScoreObservation = ScoreStore.Observation;
107
+ /**
108
+ * A batch result tagged with the identity of the job that produced it.
109
+ *
110
+ * @category models
111
+ * @since 0.1.0
112
+ */
113
+ export interface BatchResult {
114
+ readonly identity: string;
115
+ readonly observation: ScoreObservation;
116
+ }
117
+ /**
118
+ * Per-case result retained by the deterministic runner.
119
+ *
120
+ * @category models
121
+ * @since 0.1.0
122
+ */
123
+ export interface CaseResult {
124
+ readonly case: string;
125
+ readonly execution?: Execution | undefined;
126
+ readonly error?: EvalError | undefined;
127
+ readonly observations: ReadonlyArray<Observation>;
128
+ readonly trials?: Trials.Summary | undefined;
129
+ }
130
+ /**
131
+ * Stable result of a suite run.
132
+ *
133
+ * @category models
134
+ * @since 0.1.0
135
+ */
136
+ export interface RunResult {
137
+ readonly runId: string;
138
+ readonly suite: string;
139
+ readonly cases: ReadonlyArray<CaseResult>;
140
+ readonly observations: ReadonlyArray<Observation>;
141
+ readonly trials?: Trials.Aggregate | undefined;
142
+ readonly k?: number | undefined;
143
+ }
144
+ /**
145
+ * Options for a deterministic suite run.
146
+ *
147
+ * @category models
148
+ * @since 0.1.0
149
+ */
150
+ export interface RunOptions {
151
+ readonly scorer?: ScoreBatchRunner | undefined;
152
+ readonly runId: string;
153
+ readonly sampleId?: string | undefined;
154
+ readonly trials?: number | undefined;
155
+ readonly k?: number | undefined;
156
+ readonly at: string;
157
+ }
158
+ declare const Runner_base: Context.ServiceClass<Runner, "flows/evals/Runner", ScoreBatchRunner>;
159
+ /**
160
+ * Injectable batch-runner service used when a caller wants a reusable adapter.
161
+ *
162
+ * {@link run} does not require it: a run scores with `options.scorer` when one
163
+ * is passed, otherwise with this service when one is provided, otherwise
164
+ * in process through {@link makeInline}.
165
+ *
166
+ * @category services
167
+ * @since 0.1.0
168
+ */
169
+ export declare class Runner extends Runner_base {
170
+ }
171
+ /**
172
+ * Builds the in-process batch runner a run scores with by default.
173
+ *
174
+ * Each job's scorer runs in the current process, its result is checked by
175
+ * `@smthrs/scorers`' own `Scorer.validate`, and a scorer that fails becomes an
176
+ * inconclusive observation naming its cause rather than failing the run. It
177
+ * honours the {@link ScoreBatchRunner} protocol, so it is also the reference
178
+ * a custom adapter can be compared against.
179
+ *
180
+ * @category constructors
181
+ * @since 0.1.0
182
+ */
183
+ export declare const makeInline: () => ScoreBatchRunner;
184
+ /**
185
+ * Provides the in-process batch runner built by {@link makeInline}.
186
+ *
187
+ * @category layers
188
+ * @since 0.1.0
189
+ */
190
+ export declare const layerInline: Layer.Layer<Runner>;
191
+ /**
192
+ * Runs a fixed suite with bounded execution and declaration-order results.
193
+ *
194
+ * Cases run through the provided {@link CaseExecutor} at the suite's
195
+ * concurrency and are returned in declaration order. Each executor receives
196
+ * its own mutable case copy. Each scorer receives independent copies of the
197
+ * original case input, expected value, and binding data. Every execution is then
198
+ * graded by the bindings whose `appliesTo` is the flow the execution reports as
199
+ * its target, matched by reference identity, so a binding attached to another
200
+ * flow contributes no observations.
201
+ *
202
+ * Scoring goes through `options.scorer` when the caller passes one, the
203
+ * {@link Runner} service when one is provided, and {@link makeInline}
204
+ * otherwise. A case whose target failed keeps its typed error, locates itself
205
+ * with the executor's own `path` or with `cases['<name>']`, and produces no
206
+ * observations; a scorer that failed produces an inconclusive observation. The
207
+ * run itself fails only with `invalid_run_options` for a non-canonical run
208
+ * identity or timestamp, `invalid_suite` for an unusable sampling policy,
209
+ * `ambiguous_score_job` when an order-only runner cannot distinguish two jobs,
210
+ * or `scorer_protocol` for a batch runner that broke the
211
+ * {@link ScoreBatchRunner} contract.
212
+ *
213
+ * @category constructors
214
+ * @since 0.1.0
215
+ */
216
+ export declare const run: (suite: Suite, options: RunOptions) => Effect.Effect<RunResult, EvalError, CaseExecutor>;
217
+ /**
218
+ * Provides a batch runner that is never available.
219
+ *
220
+ * Every bound score under this layer becomes an inconclusive observation, which
221
+ * a gate grades as an undecidable run rather than a red. Provide it to state
222
+ * that a suite must not score, and {@link layerInline} to score in process.
223
+ *
224
+ * @category layers
225
+ * @since 0.1.0
226
+ */
227
+ export declare const layerNoop: Layer.Layer<Runner>;
228
+ export {};
229
+ //# sourceMappingURL=Runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Runner.d.ts","sourceRoot":"","sources":["../../src/Runner.ts"],"names":[],"mappings":"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"}