@hona/openeval 0.5.5 → 0.5.6

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/VERIFICATION.md CHANGED
@@ -92,6 +92,13 @@ evidence. Do not put evaluator scripts in candidate workspaces.
92
92
 
93
93
  ## Readable judgments and controls
94
94
 
95
+ Retained code judgments stay scored when a metadata-only identity change leaves
96
+ the exact self-contained executable unchanged. The reader verifies both recorded
97
+ identities and rejects changed source, external package imports, unknown metadata,
98
+ or a different execution protocol. This check makes no model calls and rewrites
99
+ no evidence. A completed code verification is still required. Package/lockfile
100
+ metadata alone must not make an unchanged historical scorecard appear unscored.
101
+
95
102
  `openeval prepare --output <new-directory>` assembles real candidate inputs and
96
103
  runs declared preparation in the candidate image, then archives the prepared
97
104
  workspace. `--only-eval` scopes it. This makes zero model calls, creates no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hona/openeval",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Code and LLM evaluations with isolated agents, recorded OpenCode data, and a results viewer",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/app/scores.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { BenchmarkDefinition, JudgeRun, Slot } from "../types";
2
+ import type { CodeJudgeDefinition } from "../judge-context";
2
3
  import { modelScore } from "../view";
3
4
  import { isScored } from "../judgment";
4
5
  import { categoryKey, inCategories } from "../criterion-categories";
6
+ import { recordedCodeMatches } from "../infra/judging/code-source";
5
7
 
6
8
  /** Scores are derived from a single selection snapshot; every eval has equal weight. */
7
9
  export function benchmarkScores(
@@ -14,12 +16,23 @@ export function benchmarkScores(
14
16
  const evals = definition.evals.filter(
15
17
  (item) => !evalId || item.id === evalId,
16
18
  );
17
- // A code judgment counts only when it ran the eval's current judge.ts to completion.
19
+ const matches = new WeakMap<CodeJudgeDefinition, WeakMap<CodeJudgeDefinition, boolean>>();
20
+ const equivalent = (expected: CodeJudgeDefinition, recorded: CodeJudgeDefinition | undefined) => {
21
+ if (!recorded) return false;
22
+ let previous = matches.get(expected);
23
+ if (!previous) { previous = new WeakMap(); matches.set(expected, previous); }
24
+ const cached = previous.get(recorded);
25
+ if (cached !== undefined) return cached;
26
+ const value = recordedCodeMatches(expected, recorded);
27
+ previous.set(recorded, value);
28
+ return value;
29
+ };
30
+ // A code judgment counts only after completed verification of the matching executable.
18
31
  const current = (item: (typeof evals)[number], slot: Slot) => {
19
32
  const judge = slot.judgeRunId ? index.get(slot.judgeRunId) : undefined;
20
33
  return !item.code ||
21
34
  (judge?.code?.state === "completed" &&
22
- judge.input.code?.hash === item.code.hash)
35
+ equivalent(item.code, judge.input.code))
23
36
  ? judge
24
37
  : undefined;
25
38
  };
@@ -10,6 +10,37 @@ const printer = new Bun.Transpiler({
10
10
  deadCodeElimination: false,
11
11
  });
12
12
 
13
+ function portableDirectories(source: string) {
14
+ return source.replace(
15
+ /^\s*var __(?:dirname|filename) = .*$/gm,
16
+ (line) => line.replace(/"(?:[^"\\]|\\.)*"/g, '""'),
17
+ );
18
+ }
19
+
20
+ /** An unchanged self-contained executable can retain judgments whose older
21
+ * identity included host package manifests and lockfiles. This is a read-only
22
+ * identity check, not a rewrite of recorded code or a changed scoring rule.
23
+ * External packages still require their exact executable identity. */
24
+ export function recordedCodeMatches(
25
+ expected: CodeJudgeDefinition,
26
+ recorded: CodeJudgeDefinition | undefined,
27
+ ) {
28
+ if (!recorded) return false;
29
+ if (recorded.hash === expected.hash) return true;
30
+ if (!expected.source.trim() || expected.source !== recorded.source ||
31
+ Object.keys(expected.dependencies).length !== 0) return false;
32
+ const imports = new Bun.Transpiler({ loader: "js" }).scan(expected.source).imports;
33
+ if (imports.some(item => !item.path.startsWith("node:") && !item.path.startsWith("bun:"))) return false;
34
+ const metadata = Object.keys(recorded.dependencies);
35
+ if (!metadata.length || metadata.some(path =>
36
+ !/(?:^|[\\/])(?:package\.json|bun\.lockb?|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$/.test(path))) return false;
37
+ // Verify both identities rather than accepting an arbitrary mismatched hash.
38
+ return CODE_JUDGE_PROTOCOL === 1 &&
39
+ recorded.hash === fingerprint({ source: recorded.source, dependencies: recorded.dependencies }) &&
40
+ expected.hash === fingerprint({ protocol: CODE_JUDGE_PROTOCOL,
41
+ source: printer.transformSync(portableDirectories(expected.source)) });
42
+ }
43
+
13
44
  /** The package that provides an external import, as `name@version/path`. */
14
45
  async function packageSpecifier(path: string, from: string) {
15
46
  for (let directory = dirname(path); ; directory = dirname(directory)) {
@@ -93,10 +124,7 @@ export async function compileCodeJudge(
93
124
  );
94
125
  }
95
126
  // Bun writes __dirname and __filename as absolute paths. Like other runtime file inputs, they are not identity.
96
- portable = portable.replace(
97
- /^\s*var __(?:dirname|filename) = .*$/gm,
98
- (line) => line.replace(/"(?:[^"\\]|\\.)*"/g, '""'),
99
- );
127
+ portable = portableDirectories(portable);
100
128
  return {
101
129
  file,
102
130
  source,