@openpond/evals 0.5.0 → 0.7.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.
Files changed (118) hide show
  1. package/CONTRACT.md +13 -1
  2. package/LEARNING.md +106 -0
  3. package/README.md +28 -2
  4. package/conformance/telemetry/v1/invalid-batch.json +17 -0
  5. package/conformance/telemetry/v1/valid-batch.json +58 -0
  6. package/dist/graders.js +26 -5
  7. package/dist/index.js +8 -0
  8. package/dist/javascript-verifier-contract.js +7 -0
  9. package/dist/javascript-verifier-node.js +56 -0
  10. package/dist/javascript-verifier-worker-source.js +1 -0
  11. package/dist/javascript-verifier-worker.js +6 -0
  12. package/dist/javascript-verifier.js +64 -0
  13. package/dist/learned-preference.js +334 -0
  14. package/dist/learning/admission.js +209 -0
  15. package/dist/learning/assets.js +37 -0
  16. package/dist/learning/contracts.js +267 -0
  17. package/dist/learning/errors.js +12 -0
  18. package/dist/learning/grade-worker.js +99 -0
  19. package/dist/learning/index.js +9 -0
  20. package/dist/learning/operations.js +33 -0
  21. package/dist/learning/repository.js +48 -0
  22. package/dist/learning/service.js +367 -0
  23. package/dist/learning/transport.js +25 -0
  24. package/dist/preferences.js +77 -11
  25. package/dist/rewards.js +230 -0
  26. package/dist/task-schema.js +142 -0
  27. package/dist/tasksets.js +3 -0
  28. package/dist/telemetry/index.js +4 -0
  29. package/dist/telemetry-analysis.js +183 -0
  30. package/dist/telemetry-bundle.js +60 -0
  31. package/dist/telemetry-catalog.js +50 -0
  32. package/dist/telemetry.js +112 -0
  33. package/dist/types/builtin-benchmarks/harness-refiner.d.ts +7 -0
  34. package/dist/types/builtin-benchmarks/harness-refiner.d.ts.map +1 -1
  35. package/dist/types/conformance.d.ts +14 -0
  36. package/dist/types/conformance.d.ts.map +1 -1
  37. package/dist/types/execution-contracts.d.ts +14 -0
  38. package/dist/types/execution-contracts.d.ts.map +1 -1
  39. package/dist/types/graders.d.ts.map +1 -1
  40. package/dist/types/index.d.ts +8 -0
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/types/javascript-verifier-contract.d.ts +9 -0
  43. package/dist/types/javascript-verifier-contract.d.ts.map +1 -0
  44. package/dist/types/javascript-verifier-node.d.ts +5 -0
  45. package/dist/types/javascript-verifier-node.d.ts.map +1 -0
  46. package/dist/types/javascript-verifier-worker-source.d.ts +2 -0
  47. package/dist/types/javascript-verifier-worker.d.ts +2 -0
  48. package/dist/types/javascript-verifier-worker.d.ts.map +1 -0
  49. package/dist/types/javascript-verifier.d.ts +15 -0
  50. package/dist/types/javascript-verifier.d.ts.map +1 -0
  51. package/dist/types/learned-preference.d.ts +282 -0
  52. package/dist/types/learned-preference.d.ts.map +1 -0
  53. package/dist/types/learning/admission.d.ts +335 -0
  54. package/dist/types/learning/admission.d.ts.map +1 -0
  55. package/dist/types/learning/assets.d.ts +49 -0
  56. package/dist/types/learning/assets.d.ts.map +1 -0
  57. package/dist/types/learning/contracts.d.ts +2149 -0
  58. package/dist/types/learning/contracts.d.ts.map +1 -0
  59. package/dist/types/learning/errors.d.ts +8 -0
  60. package/dist/types/learning/errors.d.ts.map +1 -0
  61. package/dist/types/learning/grade-worker.d.ts +31 -0
  62. package/dist/types/learning/grade-worker.d.ts.map +1 -0
  63. package/dist/types/learning/index.d.ts +10 -0
  64. package/dist/types/learning/index.d.ts.map +1 -0
  65. package/dist/types/learning/operations.d.ts +2061 -0
  66. package/dist/types/learning/operations.d.ts.map +1 -0
  67. package/dist/types/learning/repository.d.ts +1143 -0
  68. package/dist/types/learning/repository.d.ts.map +1 -0
  69. package/dist/types/learning/service.d.ts +22 -0
  70. package/dist/types/learning/service.d.ts.map +1 -0
  71. package/dist/types/learning/transport.d.ts +2139 -0
  72. package/dist/types/learning/transport.d.ts.map +1 -0
  73. package/dist/types/preferences.d.ts +55 -7
  74. package/dist/types/preferences.d.ts.map +1 -1
  75. package/dist/types/review-conformance.d.ts +5 -5
  76. package/dist/types/rewards.d.ts +611 -0
  77. package/dist/types/rewards.d.ts.map +1 -0
  78. package/dist/types/task-schema.d.ts +18 -0
  79. package/dist/types/task-schema.d.ts.map +1 -0
  80. package/dist/types/tasksets.d.ts +28 -0
  81. package/dist/types/tasksets.d.ts.map +1 -1
  82. package/dist/types/telemetry/index.d.ts +5 -0
  83. package/dist/types/telemetry/index.d.ts.map +1 -0
  84. package/dist/types/telemetry-analysis.d.ts +116 -0
  85. package/dist/types/telemetry-analysis.d.ts.map +1 -0
  86. package/dist/types/telemetry-bundle.d.ts +309 -0
  87. package/dist/types/telemetry-bundle.d.ts.map +1 -0
  88. package/dist/types/telemetry-catalog.d.ts +269 -0
  89. package/dist/types/telemetry-catalog.d.ts.map +1 -0
  90. package/dist/types/telemetry.d.ts +266 -0
  91. package/dist/types/telemetry.d.ts.map +1 -0
  92. package/package.json +40 -3
  93. package/schemas/learning/v1/asset.schema.json +89 -0
  94. package/schemas/learning/v1/batch.schema.json +132 -0
  95. package/schemas/learning/v1/binding.schema.json +207 -0
  96. package/schemas/learning/v1/command-request.schema.json +2414 -0
  97. package/schemas/learning/v1/decision.schema.json +440 -0
  98. package/schemas/learning/v1/definition.schema.json +397 -0
  99. package/schemas/learning/v1/evidence.schema.json +317 -0
  100. package/schemas/learning/v1/example-submission.schema.json +253 -0
  101. package/schemas/learning/v1/feedback-submission.schema.json +114 -0
  102. package/schemas/learning/v1/feedback.schema.json +233 -0
  103. package/schemas/learning/v1/grade.schema.json +412 -0
  104. package/schemas/learning/v1/iteration.schema.json +236 -0
  105. package/schemas/learning/v1/package.schema.json +776 -0
  106. package/schemas/learning/v1/policy.schema.json +373 -0
  107. package/schemas/learning/v1/read-request.schema.json +101 -0
  108. package/schemas/learning/v1/reward.schema.json +302 -0
  109. package/schemas/learning/v1/source.schema.json +198 -0
  110. package/schemas/telemetry/v1/evidence-completeness.schema.json +82 -0
  111. package/schemas/telemetry/v1/evidence-reference.schema.json +41 -0
  112. package/schemas/telemetry/v1/metric-definition.schema.json +96 -0
  113. package/schemas/telemetry/v1/metric-observation.schema.json +182 -0
  114. package/schemas/telemetry/v1/run-metric-summary.schema.json +98 -0
  115. package/schemas/telemetry/v1/run-telemetry-batch.schema.json +405 -0
  116. package/schemas/telemetry/v1/run-telemetry-event.schema.json +201 -0
  117. package/schemas/telemetry/v1/telemetry-cohort.schema.json +100 -0
  118. package/schemas/telemetry/v1/telemetry-export-bundle.schema.json +655 -0
package/CONTRACT.md CHANGED
@@ -4,7 +4,8 @@
4
4
  model-driven refinement/review policy, improvements, traces, tools, model
5
5
  identities, and shared hashing.
6
6
  `@openpond/evals` owns Tasksets, graders, evaluation runs and receipts,
7
- execution orchestration, conformance fixtures, and Work-evidence eligibility.
7
+ execution orchestration, conformance fixtures, Work-evidence eligibility, and
8
+ portable Run telemetry/metric semantics.
8
9
  Evals depends on Harness for exact release identities but does not re-export
9
10
  Harness APIs; Harness never depends on Evals. Host applications own persistence, provider sessions, secret
10
11
  leases, connected-app authorization, model streaming, artifact bytes, and
@@ -26,6 +27,8 @@ runtime processes.
26
27
  | user feedback on Work output | `WorkFeedbackReceipt` | Append a new receipt bound to the evidence receipt and, when selected, the exact content-addressed output-revision descriptor. Corrections are separate artifacts and never mutate prior receipts. |
27
28
  | bounded cross-Work review | `HarnessEvaluationReviewReceipt` | Select only currently authorized immutable evidence, advance one watermark, group one stable claim, route to the smallest correct layer, and name the next authority without performing downstream effects. |
28
29
  | model-improvement qualification | `ModelImprovementQualificationReceipt` | Bind the originating review, exact Harness, Taskset, real baseline Evaluation, Model, Environment/tool/permission/policy hashes, Verifier, source policies, privacy, budget, and non-frozen signal. Weak or confounded evidence emits `no_training`; training and activation remain host effects. |
30
+ | trainer/runtime telemetry | `RunTelemetryEvent` + `MetricObservation` | Emit compact ordered events and observations with exact Run lineage, bounded attributes/dimensions, source authority, visibility, and stable idempotency identity. Tenant identity, provider credentials, storage, retention, billing, and durable indexes remain host projections. |
31
+ | local/hosted Run investigation export | `TelemetryExportBundle` | Export metric definitions, ordered evidence, bounded references, completeness, and a content hash. Apply visibility and redaction before crossing authority boundaries; never include raw privileged trace bytes. |
29
32
 
30
33
  ## Compatibility policy
31
34
 
@@ -48,6 +51,15 @@ runtime processes.
48
51
  immutable objects and rejects lifecycle, tool, grader-interface, or required
49
52
  Environment-tool drift before issuing the receipt.
50
53
  - The initial support target is Node.js ESM on Node 22.14 through Node 24.
54
+ - Telemetry schema literals are shared across `@openpond/evals/telemetry` and
55
+ the `openpond-evals` Python distribution. Generated JSON Schemas and positive
56
+ and negative fixtures are the cross-language conformance authority.
57
+ - Telemetry producer sequence is monotonic within a Run. Receivers deduplicate
58
+ exact retries, accept late delivery, and reject conflicting reuse of an
59
+ idempotency key or `(runId, sequence)` pair.
60
+ - Core metrics reject unknown dimensions. Taskset- or Environment-specific
61
+ extensions require an explicit `MetricDefinition`; arbitrary metric names or
62
+ unbounded labels are not portable telemetry.
51
63
  - Portable paths are relative and at most 2,000 characters. Individual assets
52
64
  are at most 250 MB. Tasksets, traces, and evidence arrays have schema-level
53
65
  upper bounds.
package/LEARNING.md ADDED
@@ -0,0 +1,106 @@
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
+ ## Data flow
16
+
17
+ 1. Publish a Reward, then a binding, task definition and source. Use
18
+ `publish_resources` to publish dependent resources atomically. Pin an existing
19
+ release by `{ id, revision, contentHash }`; edits create the next revision with
20
+ an expected current revision.
21
+ 2. Submit task examples under a stable source/example/attempt identity. Preserve
22
+ the producer idempotency key on retries. Reusing a key with different content
23
+ fails with a conflict. Invalid task-specific evidence remains reviewable;
24
+ accepting the submission does not approve it for training.
25
+ 3. Queue durable grading of the observed output or a proposed supervised target.
26
+ Hosts execute the binding through `TaskGradeExecutor`; the built-in executor
27
+ supports portable deterministic checks and reports other implementations as
28
+ unavailable. Publication and schema validity are not execution receipts.
29
+ 4. Record a reviewer decision referencing completed grader-run IDs. The service
30
+ checks the exact task, evidence, binding and output hashes. A failed observed
31
+ response may reveal a valid task. Supervised learning additionally requires a
32
+ separately approved, schema-valid target whose required checks passed.
33
+ 5. Seal reviewed evidence into a batch. The service reserves both task-family and
34
+ exact-input identities across splits in the same transaction. Unresolved
35
+ families, stale evidence, stale decisions and held-out/training overlap fail.
36
+ `compileTaskBatch` creates the existing `openpond.tasksetRelease.v2` package.
37
+
38
+ Observed output, verifier expected output, private evaluator context and an
39
+ approved supervised target have separate fields. The public policy view excludes
40
+ expected output and private context. Compiled packages retain the approved target
41
+ in typed learning metadata for a training adapter; they do not replace verifier
42
+ ground truth with a demonstration.
43
+
44
+ Feedback addresses source/example/attempt identity and may arrive before the
45
+ example. Input, ground-truth and family corrections create new evidence revisions.
46
+ Existing batches retain their exact historical evidence. Proposed-target feedback
47
+ must be graded and reviewed before becoming a supervised target.
48
+
49
+ ## JSON and schema profile
50
+
51
+ Generated HTTP/producer schemas are in `schemas/learning/v1`. They describe the
52
+ structural contract. Use the domain service for authorization, revision/hash
53
+ identity, admission and transactional constraints; JSON Schema alone cannot prove
54
+ those relationships. Input/output schemas use object envelopes and JSON Schema
55
+ 2020-12 through Ajv, with a bounded portable profile:
56
+
57
+ - Local `#/$defs/...` references; no remote loading or recursive schemas.
58
+ - No regex, formats, custom keywords or runtime code inside schema validation.
59
+ - At most 32 KiB of schema, bounded expansion/alternatives and validation caches.
60
+ - Plain JSON values, finite numbers, no accessors/serializers/cycles, bounded depth,
61
+ node count and UTF-8 bytes. Host request limits apply in addition to field limits.
62
+
63
+ This profile follows [Ajv's security guidance](https://ajv.js.org/security.html)
64
+ for untrusted schemas and deliberately rejects unsupported capabilities.
65
+
66
+ ## Host responsibilities
67
+
68
+ Authored verifier code and rubrics use immutable `asset` resources. Publish source
69
+ and its Reward with `publish_resources`; the service checks UTF-8 size, SHA-256,
70
+ full reference identity, scope and evaluator visibility in the same transaction.
71
+ Editing source creates a new asset identity. Existing Rewards retain their bytes.
72
+
73
+ `@openpond/evals/javascript-verifier/node` executes ESM verifier source in a
74
+ worker containing a fresh QuickJS WebAssembly interpreter. The verifier receives
75
+ JSON data and returns `{ score, passed, feedback, evidenceRefs? }`. It has no host
76
+ functions or import loader. Memory, stack, source/result sizes and execution time
77
+ are bounded; cancellation settles only after the worker has terminated. The
78
+ interpreter and worker are embedded in the package, so a clean install needs no
79
+ separate WASM download or native compiler. Browser/worker hosts can use the
80
+ portable `@openpond/evals/javascript-verifier` entry point directly.
81
+
82
+ This boundary follows the [QuickJS runtime isolation and resource APIs](https://github.com/justjake/quickjs-emscripten).
83
+ Node's [VM documentation](https://nodejs.org/api/vm.html) explicitly excludes
84
+ untrusted-code isolation; the local Taskset adapter now uses the same public
85
+ interpreter worker as task-evidence grading.
86
+
87
+ Implement `LearningRepository.transaction` with serialized writes and atomic
88
+ rollback. Persist immutable historical revisions, exact operation receipts and
89
+ unique split reservations. Never share a transaction object after it closes.
90
+ List results are cursor-paginated at no more than 100 resources.
91
+
92
+ Authorize every operation. Source credentials can submit only to their assigned
93
+ source; human admission/correction/sealing require a reviewer. Derive identities
94
+ from authentication, never from a producer-supplied actor field.
95
+
96
+ Grading workers use leases, stable job identity, bounded timeout/spend and terminal
97
+ receipts. Execution adapters must deduplicate submission and confirm cleanup.
98
+ Cancellation remains `cancelling` until the execution owner confirms termination;
99
+ a UI cancellation request is not proof that compute stopped. Restart recovery
100
+ must use the same durable jobs. Unexpected host failures remain server errors;
101
+ `LearningDomainError` exposes rejected requests as typed 4xx failures.
102
+
103
+ Policy and iteration contracts distinguish training parent, optional teacher and
104
+ upstream trigger. The contracts themselves do not launch training, schedule jobs,
105
+ qualify a model, accept a candidate or promote a serving endpoint. Hosts must
106
+ implement those transitions and record actual execution/evaluation receipts.
package/README.md CHANGED
@@ -1,9 +1,12 @@
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
6
- eligibility, and no-training/SFT/preference/RL qualification receipts. The package depends on
8
+ eligibility, no-training/SFT/preference/RL qualification receipts, and portable
9
+ training telemetry. The package depends on
7
10
  [`@openpond/harness`](../harness/README.md) for exact Harness identities but
8
11
  does not re-export Harness APIs. Applications import the two packages directly,
9
12
  which keeps refinement and evaluation authority visibly separate.
@@ -34,7 +37,7 @@ import {
34
37
  ```
35
38
 
36
39
  Subpath exports are available at `/harness`, `/tasksets`, `/benchmarks`, `/graders`, `/runs`,
37
- `/conformance`, `/evidence`, `/preferences`, `/review`, and
40
+ `/conformance`, `/evidence`, `/telemetry`, `/preferences`, `/review`, and
38
41
  `/model-improvement-qualification`. The package is an evaluation protocol library,
39
42
  not a hosted client. It does not execute OpenPond Desktop or Sandbox sessions,
40
43
  resolve credentials, or persist artifacts.
@@ -122,6 +125,29 @@ semantics. Infrastructure failures must remain reward-ineligible.
122
125
  Package semver and schema literals are independent. See [CONTRACT.md](./CONTRACT.md)
123
126
  for compatibility aliases, migration rules, size limits, and the field map.
124
127
 
128
+ ## Training telemetry
129
+
130
+ The `@openpond/evals/telemetry` subpath defines the cross-runtime Run event,
131
+ metric, cohort, evidence-completeness, and export-bundle protocol. It includes a
132
+ bounded core metric catalog, deterministic builders, duplicate and late-delivery
133
+ merge semantics, chart-ready aggregation, cohort filtering, and
134
+ visibility-aware export helpers. The sibling `openpond-evals` Python
135
+ distribution implements the producer-facing models, builders, and asynchronous
136
+ buffered emitter used by GPU workers and external trainers; both languages
137
+ validate the same fixtures and schema literals.
138
+
139
+ Telemetry records what an admitted Run did; it does not configure the Taskset
140
+ or execute training. Core observations are accepted only when their metric ID
141
+ and bounded dimensions match the catalog. Custom metrics require an explicit
142
+ versioned definition. Events carry immutable Run/Model/Harness/Taskset lineage,
143
+ producer sequence, source authority, and evidence visibility.
144
+
145
+ Portable export bundles contain definitions, events, observations, bounded
146
+ evidence references, completeness state, and a content hash. Raw trace bytes,
147
+ credentials, provider handles, tenant identity, billing policy, and hosted
148
+ indexes remain host-owned. The package contains no trainer, optimizer,
149
+ provisioner, persistence client, or diagnostic agent.
150
+
125
151
  ## Release preparation
126
152
 
127
153
  ```bash
@@ -0,0 +1,17 @@
1
+ {
2
+ "schemaVersion": "openpond.runTelemetryBatch.v1",
3
+ "events": [
4
+ {
5
+ "schemaVersion": "openpond.runTelemetryEvent.v1",
6
+ "eventId": "event-invalid",
7
+ "sequence": -1,
8
+ "occurredAt": "not-a-timestamp",
9
+ "source": "unknown_source",
10
+ "type": "optimizer_step_completed",
11
+ "visibility": "team_visible",
12
+ "lineage": {},
13
+ "attributes": {}
14
+ }
15
+ ],
16
+ "observations": []
17
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "schemaVersion": "openpond.runTelemetryBatch.v1",
3
+ "events": [
4
+ {
5
+ "schemaVersion": "openpond.runTelemetryEvent.v1",
6
+ "eventId": "event-optimizer-step-1",
7
+ "sequence": 1,
8
+ "occurredAt": "2026-08-25T20:00:00.000Z",
9
+ "source": "optimizer",
10
+ "type": "optimizer_step_completed",
11
+ "visibility": "team_visible",
12
+ "lineage": {
13
+ "modelProjectId": "project-1",
14
+ "runId": "run-1",
15
+ "modelVersionId": "version-1",
16
+ "harnessReleaseHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
17
+ "tasksetReleaseHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
18
+ "environmentReleaseHash": null,
19
+ "checkpointId": null,
20
+ "step": 1,
21
+ "rolloutGroupId": "group-1",
22
+ "attemptId": null,
23
+ "scenarioId": "scenario-1"
24
+ },
25
+ "attributes": {
26
+ "learningRate": 0.00001,
27
+ "runner": "openpond_direct_grpo_v1"
28
+ }
29
+ }
30
+ ],
31
+ "observations": [
32
+ {
33
+ "schemaVersion": "openpond.metricObservation.v1",
34
+ "observationId": "observation-learning-rate-1",
35
+ "metricId": "optimizer.learning_rate",
36
+ "eventId": "event-optimizer-step-1",
37
+ "sequence": 2,
38
+ "observedAt": "2026-08-25T20:00:00.000Z",
39
+ "value": 0.00001,
40
+ "lineage": {
41
+ "modelProjectId": "project-1",
42
+ "runId": "run-1",
43
+ "modelVersionId": "version-1",
44
+ "harnessReleaseHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
45
+ "tasksetReleaseHash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
46
+ "environmentReleaseHash": null,
47
+ "checkpointId": null,
48
+ "step": 1,
49
+ "rolloutGroupId": "group-1",
50
+ "attemptId": null,
51
+ "scenarioId": "scenario-1"
52
+ },
53
+ "dimensions": {
54
+ "split": "train"
55
+ }
56
+ }
57
+ ]
58
+ }
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
@@ -6,9 +6,17 @@ export * from "./execution-contracts.js";
6
6
  export * from "./execution-receipts.js";
7
7
  export * from "./graders.js";
8
8
  export * from "./harness.js";
9
+ export * from "./learned-preference.js";
9
10
  export * from "./runs.js";
10
11
  export * from "./model-improvement-qualification.js";
11
12
  export * from "./preferences.js";
12
13
  export * from "./review-conformance.js";
13
14
  export * from "./rollouts.js";
15
+ export * from "./telemetry.js";
16
+ export * from "./telemetry-catalog.js";
17
+ export * from "./telemetry-analysis.js";
18
+ export * from "./telemetry-bundle.js";
14
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
+ }