@openpond/evals 0.6.0 → 0.7.1

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 (96) hide show
  1. package/LEARNING.md +120 -0
  2. package/README.md +2 -0
  3. package/dist/graders.js +26 -5
  4. package/dist/index.js +3 -0
  5. package/dist/javascript-verifier-contract.js +7 -0
  6. package/dist/javascript-verifier-node.js +56 -0
  7. package/dist/javascript-verifier-worker-source.js +1 -0
  8. package/dist/javascript-verifier-worker.js +6 -0
  9. package/dist/javascript-verifier.js +64 -0
  10. package/dist/learning/admission.js +209 -0
  11. package/dist/learning/assets.js +37 -0
  12. package/dist/learning/contracts.js +267 -0
  13. package/dist/learning/errors.js +12 -0
  14. package/dist/learning/grade-worker.js +99 -0
  15. package/dist/learning/index.js +9 -0
  16. package/dist/learning/operations.js +33 -0
  17. package/dist/learning/repository.js +48 -0
  18. package/dist/learning/service.js +377 -0
  19. package/dist/learning/transport.js +35 -0
  20. package/dist/rewards.js +230 -0
  21. package/dist/task-schema-meta-validator.js +2 -0
  22. package/dist/task-schema-validation.js +113 -0
  23. package/dist/task-schema.js +4 -0
  24. package/dist/task-value-validator.js +37 -0
  25. package/dist/tasksets.js +3 -0
  26. package/dist/telemetry-catalog.js +1 -1
  27. package/dist/types/builtin-benchmarks/harness-refiner.d.ts +7 -0
  28. package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -1
  29. package/dist/types/conformance.d.ts +14 -0
  30. package/dist/types/conformance.d.ts.map +1 -1
  31. package/dist/types/execution-contracts.d.ts +14 -0
  32. package/dist/types/execution-contracts.d.ts.map +1 -1
  33. package/dist/types/graders.d.ts.map +1 -1
  34. package/dist/types/index.d.ts +3 -0
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/types/javascript-verifier-contract.d.ts +9 -0
  37. package/dist/types/javascript-verifier-contract.d.ts.map +1 -0
  38. package/dist/types/javascript-verifier-node.d.ts +5 -0
  39. package/dist/types/javascript-verifier-node.d.ts.map +1 -0
  40. package/dist/types/javascript-verifier-worker-source.d.ts +2 -0
  41. package/dist/types/javascript-verifier-worker.d.ts +2 -0
  42. package/dist/types/javascript-verifier-worker.d.ts.map +1 -0
  43. package/dist/types/javascript-verifier.d.ts +15 -0
  44. package/dist/types/javascript-verifier.d.ts.map +1 -0
  45. package/dist/types/learning/admission.d.ts +335 -0
  46. package/dist/types/learning/admission.d.ts.map +1 -0
  47. package/dist/types/learning/assets.d.ts +49 -0
  48. package/dist/types/learning/assets.d.ts.map +1 -0
  49. package/dist/types/learning/contracts.d.ts +2149 -0
  50. package/dist/types/learning/contracts.d.ts.map +1 -0
  51. package/dist/types/learning/errors.d.ts +8 -0
  52. package/dist/types/learning/errors.d.ts.map +1 -0
  53. package/dist/types/learning/grade-worker.d.ts +31 -0
  54. package/dist/types/learning/grade-worker.d.ts.map +1 -0
  55. package/dist/types/learning/index.d.ts +10 -0
  56. package/dist/types/learning/index.d.ts.map +1 -0
  57. package/dist/types/learning/operations.d.ts +2061 -0
  58. package/dist/types/learning/operations.d.ts.map +1 -0
  59. package/dist/types/learning/repository.d.ts +1143 -0
  60. package/dist/types/learning/repository.d.ts.map +1 -0
  61. package/dist/types/learning/service.d.ts +36 -0
  62. package/dist/types/learning/service.d.ts.map +1 -0
  63. package/dist/types/learning/transport.d.ts +2173 -0
  64. package/dist/types/learning/transport.d.ts.map +1 -0
  65. package/dist/types/preferences.d.ts +3 -3
  66. package/dist/types/review-conformance.d.ts +5 -5
  67. package/dist/types/rewards.d.ts +611 -0
  68. package/dist/types/rewards.d.ts.map +1 -0
  69. package/dist/types/task-schema-meta-validator.d.ts +3 -0
  70. package/dist/types/task-schema-validation.d.ts +18 -0
  71. package/dist/types/task-schema-validation.d.ts.map +1 -0
  72. package/dist/types/task-schema.d.ts +3 -0
  73. package/dist/types/task-schema.d.ts.map +1 -0
  74. package/dist/types/task-value-validator.d.ts +3 -0
  75. package/dist/types/task-value-validator.d.ts.map +1 -0
  76. package/dist/types/tasksets.d.ts +28 -0
  77. package/dist/types/tasksets.d.ts.map +1 -1
  78. package/package.json +30 -3
  79. package/schemas/learning/v1/asset.schema.json +89 -0
  80. package/schemas/learning/v1/batch.schema.json +132 -0
  81. package/schemas/learning/v1/binding.schema.json +207 -0
  82. package/schemas/learning/v1/command-request.schema.json +2414 -0
  83. package/schemas/learning/v1/decision.schema.json +440 -0
  84. package/schemas/learning/v1/definition.schema.json +397 -0
  85. package/schemas/learning/v1/evidence-inspection.schema.json +106 -0
  86. package/schemas/learning/v1/evidence.schema.json +317 -0
  87. package/schemas/learning/v1/example-submission.schema.json +253 -0
  88. package/schemas/learning/v1/feedback-submission.schema.json +114 -0
  89. package/schemas/learning/v1/feedback.schema.json +233 -0
  90. package/schemas/learning/v1/grade.schema.json +412 -0
  91. package/schemas/learning/v1/iteration.schema.json +236 -0
  92. package/schemas/learning/v1/package.schema.json +776 -0
  93. package/schemas/learning/v1/policy.schema.json +373 -0
  94. package/schemas/learning/v1/read-request.schema.json +142 -0
  95. package/schemas/learning/v1/reward.schema.json +302 -0
  96. package/schemas/learning/v1/source.schema.json +198 -0
package/LEARNING.md ADDED
@@ -0,0 +1,120 @@
1
+ # Task evidence and reusable Rewards
2
+
3
+ `@openpond/evals/learning` owns the portable data and domain rules for task
4
+ definitions, sources, submissions, feedback, admission, immutable task batches,
5
+ learning policies and grading jobs. `@openpond/evals/rewards` owns immutable
6
+ Reward releases and exact versioned bindings. The package does not contain a
7
+ database, credentials, cloud client or execution infrastructure.
8
+
9
+ Use `openpond-sdk/learning` to submit the same commands to an authenticated local
10
+ or hosted execution owner. Plain HTTP producers use `POST /v1/learning/commands`
11
+ with `{ "scope": "<profile-or-team>", "command": { ... } }` and a Bearer token.
12
+ Reads use `POST /v1/learning/read`. Scope is a requested ownership boundary; the
13
+ host must authorize it from the credential and assign the actor itself.
14
+
15
+ Task-schema authoring uses a precompiled JSON Schema 2020-12 meta-validator plus
16
+ the bounded keyword/reference checks. Parsing definitions in a browser requires
17
+ no runtime code generation and works under a Content Security Policy without
18
+ `unsafe-eval`. The execution owner separately compiles schemas when validating
19
+ submitted values; schema publication alone is not evidence that an example passes.
20
+
21
+ Browser review uses `client.inspectEvidence({ id, revision, contentHash })`, or
22
+ the read payload `{ "action": "inspect_evidence", "scope": "<profile-or-team>",
23
+ "evidence": { "id": "...", "revision": 1, "contentHash": "..." } }`. The
24
+ execution owner checks the exact stored evidence and task definition and returns
25
+ their references with task readiness, observed-output validity and schema issues.
26
+ Source-only credentials cannot inspect evidence. Inspection is read-only; grade
27
+ and approval operations still validate their authoritative inputs independently.
28
+
29
+ ## Data flow
30
+
31
+ 1. Publish a Reward, then a binding, task definition and source. Use
32
+ `publish_resources` to publish dependent resources atomically. Pin an existing
33
+ release by `{ id, revision, contentHash }`; edits create the next revision with
34
+ an expected current revision.
35
+ 2. Submit task examples under a stable source/example/attempt identity. Preserve
36
+ the producer idempotency key on retries. Reusing a key with different content
37
+ fails with a conflict. Invalid task-specific evidence remains reviewable;
38
+ accepting the submission does not approve it for training.
39
+ 3. Queue durable grading of the observed output or a proposed supervised target.
40
+ Hosts execute the binding through `TaskGradeExecutor`; the built-in executor
41
+ supports portable deterministic checks and reports other implementations as
42
+ unavailable. Publication and schema validity are not execution receipts.
43
+ 4. Record a reviewer decision referencing completed grader-run IDs. The service
44
+ checks the exact task, evidence, binding and output hashes. A failed observed
45
+ response may reveal a valid task. Supervised learning additionally requires a
46
+ separately approved, schema-valid target whose required checks passed.
47
+ 5. Seal reviewed evidence into a batch. The service reserves both task-family and
48
+ exact-input identities across splits in the same transaction. Unresolved
49
+ families, stale evidence, stale decisions and held-out/training overlap fail.
50
+ `compileTaskBatch` creates the existing `openpond.tasksetRelease.v2` package.
51
+
52
+ Observed output, verifier expected output, private evaluator context and an
53
+ approved supervised target have separate fields. The public policy view excludes
54
+ expected output and private context. Compiled packages retain the approved target
55
+ in typed learning metadata for a training adapter; they do not replace verifier
56
+ ground truth with a demonstration.
57
+
58
+ Feedback addresses source/example/attempt identity and may arrive before the
59
+ example. Input, ground-truth and family corrections create new evidence revisions.
60
+ Existing batches retain their exact historical evidence. Proposed-target feedback
61
+ must be graded and reviewed before becoming a supervised target.
62
+
63
+ ## JSON and schema profile
64
+
65
+ Generated HTTP/producer schemas are in `schemas/learning/v1`. They describe the
66
+ structural contract. Use the domain service for authorization, revision/hash
67
+ identity, admission and transactional constraints; JSON Schema alone cannot prove
68
+ those relationships. Input/output schemas use object envelopes and JSON Schema
69
+ 2020-12 through Ajv, with a bounded portable profile:
70
+
71
+ - Local `#/$defs/...` references; no remote loading or recursive schemas.
72
+ - No regex, formats, custom keywords or runtime code inside schema validation.
73
+ - At most 32 KiB of schema, bounded expansion/alternatives and validation caches.
74
+ - Plain JSON values, finite numbers, no accessors/serializers/cycles, bounded depth,
75
+ node count and UTF-8 bytes. Host request limits apply in addition to field limits.
76
+
77
+ This profile follows [Ajv's security guidance](https://ajv.js.org/security.html)
78
+ for untrusted schemas and deliberately rejects unsupported capabilities.
79
+
80
+ ## Host responsibilities
81
+
82
+ Authored verifier code and rubrics use immutable `asset` resources. Publish source
83
+ and its Reward with `publish_resources`; the service checks UTF-8 size, SHA-256,
84
+ full reference identity, scope and evaluator visibility in the same transaction.
85
+ Editing source creates a new asset identity. Existing Rewards retain their bytes.
86
+
87
+ `@openpond/evals/javascript-verifier/node` executes ESM verifier source in a
88
+ worker containing a fresh QuickJS WebAssembly interpreter. The verifier receives
89
+ JSON data and returns `{ score, passed, feedback, evidenceRefs? }`. It has no host
90
+ functions or import loader. Memory, stack, source/result sizes and execution time
91
+ are bounded; cancellation settles only after the worker has terminated. The
92
+ interpreter and worker are embedded in the package, so a clean install needs no
93
+ separate WASM download or native compiler. Browser/worker hosts can use the
94
+ portable `@openpond/evals/javascript-verifier` entry point directly.
95
+
96
+ This boundary follows the [QuickJS runtime isolation and resource APIs](https://github.com/justjake/quickjs-emscripten).
97
+ Node's [VM documentation](https://nodejs.org/api/vm.html) explicitly excludes
98
+ untrusted-code isolation; the local Taskset adapter now uses the same public
99
+ interpreter worker as task-evidence grading.
100
+
101
+ Implement `LearningRepository.transaction` with serialized writes and atomic
102
+ rollback. Persist immutable historical revisions, exact operation receipts and
103
+ unique split reservations. Never share a transaction object after it closes.
104
+ List results are cursor-paginated at no more than 100 resources.
105
+
106
+ Authorize every operation. Source credentials can submit only to their assigned
107
+ source; human admission/correction/sealing require a reviewer. Derive identities
108
+ from authentication, never from a producer-supplied actor field.
109
+
110
+ Grading workers use leases, stable job identity, bounded timeout/spend and terminal
111
+ receipts. Execution adapters must deduplicate submission and confirm cleanup.
112
+ Cancellation remains `cancelling` until the execution owner confirms termination;
113
+ a UI cancellation request is not proof that compute stopped. Restart recovery
114
+ must use the same durable jobs. Unexpected host failures remain server errors;
115
+ `LearningDomainError` exposes rejected requests as typed 4xx failures.
116
+
117
+ Policy and iteration contracts distinguish training parent, optional teacher and
118
+ upstream trigger. The contracts themselves do not launch training, schedule jobs,
119
+ qualify a model, accept a candidate or promote a serving endpoint. Hosts must
120
+ implement those transitions and record actual execution/evaluation receipts.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # `@openpond/evals`
2
2
 
3
+ See [task evidence and learning](./LEARNING.md) for reusable Rewards, task-format publication, SDK/HTTP intake, grading, review and approved batches.
4
+
3
5
  Portable evaluation and benchmark contracts plus pure helpers for Tasksets,
4
6
  graders, run manifests, attempt and evaluation receipts, paired benchmark
5
7
  comparisons, execution adapters, conformance fixtures, Work-evidence
package/dist/graders.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { FailureClassSchema, ReleaseHashSchema, ReleaseIdSchema, contentHash } from "@openpond/harness";
3
+ import { validateTaskSchema, validateTaskValue } from "./task-schema.js";
3
4
  export const GraderEvidenceContentSchema = z.object({
4
5
  schemaVersion: z.literal("openpond.graderEvidence.v1"),
5
6
  graderId: ReleaseIdSchema,
@@ -48,22 +49,42 @@ function gradeDeterministic(grader, task, attempt) {
48
49
  const outputField = string(config.outputField) ?? "text";
49
50
  const actual = string(attempt.output[outputField]);
50
51
  const expected = string(config.expectedValue) ?? string(task.expectedOutput?.[string(config.expectedField) ?? "text"]);
52
+ if (expected === null || !normalize(expected))
53
+ return unavailable("Content grading requires a nonempty expected value.");
51
54
  passed = actual !== null && expected !== null && normalize(actual) === normalize(expected);
52
55
  }
53
56
  else if (grader.kind === "schema") {
54
- passed = strings(config.requiredKeys).every((key) => Object.hasOwn(attempt.output, key));
57
+ if (config.jsonSchema !== undefined) {
58
+ const schema = validateTaskSchema(config.jsonSchema);
59
+ if (!schema.valid)
60
+ return unavailable(schema.issues[0].message);
61
+ passed = validateTaskValue(config.jsonSchema, attempt.output).valid;
62
+ }
63
+ else {
64
+ const required = strings(config.requiredKeys);
65
+ if (!required.length)
66
+ return unavailable("Schema grading requires a JSON Schema or explicit required keys.");
67
+ passed = required.every((key) => Object.hasOwn(attempt.output, key));
68
+ }
55
69
  }
56
70
  else if (grader.kind === "artifact") {
57
71
  const contains = string(config.refIncludes) ?? "";
72
+ if (!contains)
73
+ return unavailable("Artifact grading requires a declared artifact reference.");
58
74
  passed = attempt.artifactRefs.some((ref) => ref.includes(contains));
59
75
  }
60
76
  else if (grader.kind === "runtime_event") {
61
- passed = strings(config.requiredEvents).every((required) => attempt.runtimeEventRefs.some((ref) => ref.includes(required)));
77
+ const requiredEvents = strings(config.requiredEvents);
78
+ if (!requiredEvents.length)
79
+ return unavailable("Runtime event grading requires declared events.");
80
+ passed = requiredEvents.every((required) => attempt.runtimeEventRefs.some((ref) => ref.includes(required)));
62
81
  }
63
82
  else {
64
83
  const fields = strings(config.fields);
65
84
  const compared = fields.length ? fields : Object.keys(task.expectedOutput ?? {});
66
- passed = compared.every((field) => Object.is(attempt.output[field], task.expectedOutput?.[field]));
85
+ if (!compared.length || compared.some((field) => !task.expectedOutput || !Object.hasOwn(task.expectedOutput, field)))
86
+ return unavailable("State grading requires expected values for every compared field.");
87
+ passed = compared.every((field) => Object.hasOwn(attempt.output, field) && contentHash(attempt.output[field]) === contentHash(task.expectedOutput[field]));
67
88
  }
68
89
  return {
69
90
  score: passed ? 1 : 0,
@@ -81,7 +102,7 @@ function evidence(grader, result) {
81
102
  graderId: grader.id,
82
103
  graderVersion: grader.version,
83
104
  ...result,
84
- rewardEligible: grader.rewardEligible && result.score !== null,
105
+ rewardEligible: grader.rewardEligible && result.rewardEligible && result.score !== null,
85
106
  });
86
107
  return GraderEvidenceSchema.parse({ ...content, contentHash: contentHash(content) });
87
108
  }
@@ -89,5 +110,5 @@ function unavailable(message) {
89
110
  return { score: null, passed: false, rewardEligible: false, failureClass: "grader_failure", feedback: [message], visibleEvidenceRefs: [], privilegedEvidenceRefs: [] };
90
111
  }
91
112
  function string(value) { return typeof value === "string" ? value : null; }
92
- function strings(value) { return Array.isArray(value) ? value.filter((item) => typeof item === "string") : []; }
113
+ function strings(value) { return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : []; }
93
114
  function normalize(value) { return value.normalize("NFKC").trim().replace(/[,,]/g, "").replace(/[.\s]+$/g, "").replace(/\s+/g, " "); }
package/dist/index.js CHANGED
@@ -17,3 +17,6 @@ export * from "./telemetry-catalog.js";
17
17
  export * from "./telemetry-analysis.js";
18
18
  export * from "./telemetry-bundle.js";
19
19
  export * from "./tasksets.js";
20
+ export * from "./task-schema.js";
21
+ export * from "./rewards.js";
22
+ export * from "./learning/index.js";
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export const JavaScriptVerifierResultSchema = z.object({
3
+ score: z.number().finite().min(0).max(1),
4
+ passed: z.boolean(),
5
+ feedback: z.string().max(20_000),
6
+ evidenceRefs: z.array(z.string().max(240)).max(1_000).default([]),
7
+ }).strict();
@@ -0,0 +1,56 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { JavaScriptVerifierResultSchema } from "./javascript-verifier-contract.js";
3
+ import { javascriptVerifierWorkerSource } from "./javascript-verifier-worker-source.js";
4
+ import { assertBoundedTaskJson } from "./task-schema.js";
5
+ /** Resolves/rejects only after the execution owner has stopped the worker. */
6
+ export async function executeJavaScriptVerifierInWorker(input) {
7
+ input.signal?.throwIfAborted();
8
+ if (!Number.isInteger(input.timeoutMs) || input.timeoutMs < 1 || input.timeoutMs > 300_000)
9
+ throw new Error("verifier_timeout_invalid");
10
+ assertBoundedTaskJson(input.value, 4_194_304);
11
+ if (new TextEncoder().encode(input.source).byteLength > 524_288)
12
+ throw new Error("verifier_source_too_large");
13
+ const { signal, ...workerData } = input;
14
+ return new Promise((resolve, reject) => {
15
+ const worker = new Worker(javascriptVerifierWorkerSource, {
16
+ eval: true, workerData, execArgv: [], env: {},
17
+ resourceLimits: { maxOldGenerationSizeMb: 64, maxYoungGenerationSizeMb: 16, stackSizeMb: 4 },
18
+ });
19
+ let settling = false;
20
+ const finish = (outcome) => {
21
+ if (settling)
22
+ return;
23
+ settling = true;
24
+ clearTimeout(timer);
25
+ signal?.removeEventListener("abort", cancel);
26
+ void worker.terminate().then(() => {
27
+ if ("error" in outcome)
28
+ reject(outcome.error);
29
+ else
30
+ resolve(outcome.result);
31
+ }, reject);
32
+ };
33
+ const cancel = () => finish({ error: signal?.reason instanceof Error ? signal.reason : new Error("verifier_cancelled") });
34
+ const timer = setTimeout(() => finish({ error: new Error("verifier_timeout") }), input.timeoutMs);
35
+ signal?.addEventListener("abort", cancel, { once: true });
36
+ worker.once("error", (error) => finish({ error }));
37
+ worker.once("exit", () => { if (!settling)
38
+ finish({ error: new Error("verifier_worker_exited_without_result") }); });
39
+ worker.once("message", (message) => {
40
+ try {
41
+ if (!message || typeof message !== "object" || !("ok" in message))
42
+ throw new Error("verifier_worker_invalid_response");
43
+ if (message.ok !== true)
44
+ throw new Error("error" in message && typeof message.error === "string" ? message.error : "verifier_execution_failed");
45
+ if (!("result" in message))
46
+ throw new Error("verifier_worker_invalid_response");
47
+ finish({ result: JavaScriptVerifierResultSchema.parse(message.result) });
48
+ }
49
+ catch (error) {
50
+ finish({ error: error instanceof Error ? error : new Error("verifier_worker_invalid_response") });
51
+ }
52
+ });
53
+ if (signal?.aborted)
54
+ cancel();
55
+ });
56
+ }