@openpond/evals 0.12.6 → 0.12.7
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/LEARNING.md +5 -0
- package/dist/javascript-isolate-process-source.js +1 -1
- package/dist/javascript-verifier-worker-source.js +1 -1
- package/dist/learning/iteration-dispatch-contracts.js +4 -0
- package/dist/learning/iteration-dispatch-worker.js +4 -0
- package/dist/learning/iteration-eligibility.js +2 -1
- package/dist/learning/iteration-exposure.js +30 -0
- package/dist/learning/iteration-reservation-cancellation.js +12 -3
- package/dist/learning/iteration-reservation-contracts.js +5 -3
- package/dist/learning/iteration-reservation-service.js +10 -2
- package/dist/learning/iteration-training-parent.js +3 -1
- package/dist/learning/service.js +2 -0
- package/dist/sql-execution-process-source.js +1 -1
- package/dist/task-schema-validation.js +10 -2
- package/dist/types/learning/iteration-dispatch-contracts.d.ts +13 -0
- package/dist/types/learning/iteration-dispatch-contracts.d.ts.map +1 -1
- package/dist/types/learning/iteration-dispatch-worker.d.ts.map +1 -1
- package/dist/types/learning/iteration-eligibility.d.ts.map +1 -1
- package/dist/types/learning/iteration-exposure.d.ts +13 -0
- package/dist/types/learning/iteration-exposure.d.ts.map +1 -0
- package/dist/types/learning/iteration-reservation-cancellation.d.ts.map +1 -1
- package/dist/types/learning/iteration-reservation-contracts.d.ts +13 -3
- package/dist/types/learning/iteration-reservation-contracts.d.ts.map +1 -1
- package/dist/types/learning/iteration-reservation-service.d.ts.map +1 -1
- package/dist/types/learning/iteration-training-parent.d.ts.map +1 -1
- package/dist/types/learning/repository.d.ts +18 -2
- package/dist/types/learning/repository.d.ts.map +1 -1
- package/dist/types/learning/service.d.ts.map +1 -1
- package/dist/types/learning/transport.d.ts +1004 -994
- package/dist/types/learning/transport.d.ts.map +1 -1
- package/dist/types/task-schema-validation.d.ts.map +1 -1
- package/package.json +1 -1
- package/schemas/learning/v1/consumption.schema.json +40 -4
|
@@ -89,7 +89,11 @@ export const LearningIterationObservationSchema = z.object({
|
|
|
89
89
|
evaluation: ImmutableReleaseRefSchema.nullable(),
|
|
90
90
|
candidate: ImmutableReleaseRefSchema.nullable(),
|
|
91
91
|
failure: z.string().max(20_000).nullable(),
|
|
92
|
+
coverage: z.array(z.object({ evidence: LearningRevisionRefSchema,
|
|
93
|
+
outcome: z.enum(["trained", "exposed", "unexposed", "unknown"]) }).strict()).max(10_000).optional(),
|
|
92
94
|
}).strict().superRefine((value, context) => {
|
|
95
|
+
if (value.coverage && (!["succeeded", "failed", "cancelled"].includes(value.status) || !value.cleanupComplete || !value.receipt))
|
|
96
|
+
context.addIssue({ code: "custom", path: ["coverage"], message: "Coverage requires authoritative terminal cleanup evidence." });
|
|
93
97
|
if (["succeeded", "failed", "cancelled"].includes(value.status) && (!value.cleanupComplete || !value.receipt)) {
|
|
94
98
|
context.addIssue({ code: "custom", path: ["receipt"], message: "Terminal execution requires an authoritative cleanup receipt." });
|
|
95
99
|
}
|
|
@@ -6,6 +6,7 @@ import { LearningIterationAdmissionSchema, LearningIterationDispatchSchema, Lear
|
|
|
6
6
|
import { requireLearningRelease, requireLearningResource } from "./repository.js";
|
|
7
7
|
import { deferLearningIterationSubmission } from "./iteration-dispatch-deferral.js";
|
|
8
8
|
import { LearningIterationSubmissionDeferralSchema } from "./iteration-dispatch-contracts.js";
|
|
9
|
+
import { settleLearningIterationExposure } from "./iteration-exposure.js";
|
|
9
10
|
const closed = new Set(["accepted", "rejected", "failed", "cancelled", "completed_without_candidate"]);
|
|
10
11
|
export function createLearningIterationWorker(repository, executor, options) {
|
|
11
12
|
const now = options.now ?? (() => new Date().toISOString());
|
|
@@ -85,6 +86,9 @@ export function createLearningIterationWorker(repository, executor, options) {
|
|
|
85
86
|
terminalReceipt: outcome?.receipt ?? null, leaseOwner: null, leaseExpiresAt: null, admission: null,
|
|
86
87
|
nextAttemptAt: terminal ? null : new Date(Date.parse(now()) + 5_000).toISOString(), consecutiveFailures: 0, lastError: null });
|
|
87
88
|
if (terminal) {
|
|
89
|
+
await settleLearningIterationExposure(tx, { chainId: current.chainId, iterationId: iteration.id,
|
|
90
|
+
batch: await requireLearningRelease(tx, "batch", current.batch), outcome,
|
|
91
|
+
unstarted: !outcome && !current.submissionStartedAt });
|
|
88
92
|
const reservation = await requireLearningResource(tx, "reservation", iteration.id);
|
|
89
93
|
const settled = LearningIterationReservationSchema.parse({ ...reservation, revision: reservation.revision + 1,
|
|
90
94
|
budget: { ...reservation.budget, reservedSpendUsd: 0, settledSpendUsd: updated.spendUsd, settledAt: now() }, updatedAt: now() });
|
|
@@ -46,7 +46,8 @@ export async function inspectIterationEligibility(transaction, policy, options =
|
|
|
46
46
|
counts.excluded += 1;
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
const consumption = await transaction.get("consumption", learningConsumptionId(chainId, evidence));
|
|
50
|
+
if (consumption && consumption.exposure !== "unexposed") {
|
|
50
51
|
counts.consumed += 1;
|
|
51
52
|
continue;
|
|
52
53
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { contentHash } from "@openpond/harness";
|
|
2
|
+
import { sameLearningRef } from "./contracts.js";
|
|
3
|
+
import { LearningDomainError } from "./errors.js";
|
|
4
|
+
import { LearningConsumptionSchema } from "./iteration-reservation-contracts.js";
|
|
5
|
+
/** Called only in fenced terminal settlement. Absence/timeout is never evidence
|
|
6
|
+
* of zero exposure; unresolved rows keep their original reservation. */
|
|
7
|
+
export async function settleLearningIterationExposure(tx, input) {
|
|
8
|
+
const coverage = input.outcome?.coverage;
|
|
9
|
+
if (!input.unstarted && !coverage)
|
|
10
|
+
return;
|
|
11
|
+
if (coverage && (coverage.length !== input.batch.examples.length ||
|
|
12
|
+
new Set(coverage.map(entry => contentHash(entry.evidence))).size !== coverage.length ||
|
|
13
|
+
coverage.some(entry => !input.batch.examples.some(example => sameLearningRef(example.evidence, entry.evidence)))))
|
|
14
|
+
throw new LearningDomainError("learning_execution_coverage_mismatch", 409);
|
|
15
|
+
for (const example of input.batch.examples) {
|
|
16
|
+
const id = `consumption-${contentHash([input.chainId, example.evidence.id, example.evidence.revision])}`;
|
|
17
|
+
const current = await tx.get("consumption", id);
|
|
18
|
+
if (!current || current.iterationId !== input.iterationId || !sameLearningRef(current.evidence, example.evidence))
|
|
19
|
+
throw new LearningDomainError("learning_execution_consumption_mismatch", 409);
|
|
20
|
+
const exposure = input.unstarted ? "unexposed" : coverage.find(entry => sameLearningRef(entry.evidence, example.evidence)).outcome;
|
|
21
|
+
if (exposure === "unknown")
|
|
22
|
+
continue;
|
|
23
|
+
// A terminal replay cannot downgrade already proven numerical exposure.
|
|
24
|
+
if (["trained", "exposed"].includes(current.exposure ?? "") && exposure !== current.exposure)
|
|
25
|
+
throw new LearningDomainError("learning_execution_coverage_conflict", 409);
|
|
26
|
+
const next = LearningConsumptionSchema.parse({ ...current, revision: current.revision + 1,
|
|
27
|
+
exposure, executionReceipt: input.outcome?.receipt ?? null });
|
|
28
|
+
await tx.put("consumption", next, current.revision, { parentId: input.chainId });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { LearningIterationSchema } from "./contracts.js";
|
|
2
2
|
import { LearningDomainError } from "./errors.js";
|
|
3
3
|
import { LearningChainSchema, LearningIterationReservationSchema } from "./iteration-reservation-contracts.js";
|
|
4
|
-
import { LearningConflictError, requireLearningResource } from "./repository.js";
|
|
4
|
+
import { LearningConflictError, requireLearningRelease, requireLearningResource } from "./repository.js";
|
|
5
|
+
import { settleLearningIterationExposure } from "./iteration-exposure.js";
|
|
5
6
|
/** Only a reservation that has never been dispatched can release spend locally.
|
|
6
7
|
* Submitted executions require authoritative cancellation and settlement. */
|
|
7
8
|
export async function cancelLearningIterationReservation(transaction, input, now) {
|
|
@@ -14,6 +15,9 @@ export async function cancelLearningIterationReservation(transaction, input, now
|
|
|
14
15
|
if (iteration.trainingJob || iteration.evaluationJob || !["ready", "waiting_for_data", "waiting_for_review"].includes(iteration.status)) {
|
|
15
16
|
throw new LearningDomainError("learning_execution_cancellation_required", 409);
|
|
16
17
|
}
|
|
18
|
+
const dispatch = await transaction.get("dispatch", iteration.dispatchId);
|
|
19
|
+
if (dispatch?.submissionStartedAt || dispatch?.execution || dispatch?.leaseOwner)
|
|
20
|
+
throw new LearningDomainError("learning_execution_cancellation_required", 409);
|
|
17
21
|
const updated = LearningIterationSchema.parse({ ...iteration, revision: iteration.revision + 1, status: "cancelled", updatedAt: now });
|
|
18
22
|
const released = LearningIterationReservationSchema.parse({
|
|
19
23
|
...reservation, revision: reservation.revision + 1,
|
|
@@ -25,7 +29,12 @@ export async function cancelLearningIterationReservation(transaction, input, now
|
|
|
25
29
|
if (chain.activeIterationId === iteration.id) {
|
|
26
30
|
await transaction.put("chain", LearningChainSchema.parse({ ...chain, revision: chain.revision + 1, activeIterationId: null, updatedAt: now }), chain.revision, { parentId: chain.modelProjectId });
|
|
27
31
|
}
|
|
28
|
-
//
|
|
29
|
-
//
|
|
32
|
+
// Retain the reservation history; only this transaction's fenced proof that
|
|
33
|
+
// dispatch never started makes its exact evidence eligible again.
|
|
34
|
+
if (iteration.batch)
|
|
35
|
+
await settleLearningIterationExposure(transaction, {
|
|
36
|
+
chainId: reservation.chainId, iterationId: iteration.id,
|
|
37
|
+
batch: await requireLearningRelease(transaction, "batch", iteration.batch), outcome: null, unstarted: true,
|
|
38
|
+
});
|
|
30
39
|
return [{ kind: "iteration", id: updated.id, revision: updated.revision }, { kind: "reservation", id: released.id, revision: released.revision }];
|
|
31
40
|
}
|
|
@@ -24,17 +24,19 @@ export const LearningEligibilityCountsSchema = z.object({
|
|
|
24
24
|
excluded: z.number().int().nonnegative(),
|
|
25
25
|
consumed: z.number().int().nonnegative(),
|
|
26
26
|
}).strict();
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
27
|
+
/** Reservation history is retained; authoritative terminal exposure alone may
|
|
28
|
+
* release proven-unexposed evidence. Ambiguous execution remains reserved. */
|
|
29
29
|
export const LearningConsumptionSchema = z.object({
|
|
30
30
|
schemaVersion: z.literal("openpond.learningConsumption.v1"),
|
|
31
31
|
id: ReleaseIdSchema,
|
|
32
|
-
revision: z.
|
|
32
|
+
revision: z.number().int().positive(),
|
|
33
33
|
chainId: ReleaseIdSchema,
|
|
34
34
|
iterationId: ReleaseIdSchema,
|
|
35
35
|
evidence: LearningRevisionRefSchema,
|
|
36
36
|
decision: LearningRevisionRefSchema,
|
|
37
37
|
reservedAt: ReleaseTimestampSchema,
|
|
38
|
+
exposure: z.enum(["reserved", "trained", "exposed", "unexposed"]).optional(),
|
|
39
|
+
executionReceipt: z.object({ id: ReleaseIdSchema, contentHash: ReleaseHashSchema }).strict().nullable().optional(),
|
|
38
40
|
}).strict();
|
|
39
41
|
/** Dispatch must reconcile this reservation, never reserve a second batch on retry. */
|
|
40
42
|
export const LearningIterationReservationSchema = z.object({
|
|
@@ -24,6 +24,8 @@ export async function reserveLearningIteration(transaction, input, actorId, now)
|
|
|
24
24
|
return [{ kind: "iteration", id, revision: (await requireLearningResource(transaction, "iteration", id)).revision }, { kind: "reservation", id, revision: previous.revision }];
|
|
25
25
|
}
|
|
26
26
|
const current = await requireLearningResource(transaction, "policy", policy.id);
|
|
27
|
+
if (policy.training.replayBatches.length)
|
|
28
|
+
throw new LearningDomainError("learning_replay_not_supported", 422);
|
|
27
29
|
if (!sameLearningRef(learningRef(current), input.policy))
|
|
28
30
|
throw new LearningDomainError("learning_policy_revision_stale", 409);
|
|
29
31
|
if (!policy.enabled)
|
|
@@ -58,6 +60,7 @@ export async function reserveLearningIteration(transaction, input, actorId, now)
|
|
|
58
60
|
const parent = await resolveLearningTrainingParent(transaction, policy, acceptedParent);
|
|
59
61
|
let batch = null;
|
|
60
62
|
if (ready) {
|
|
63
|
+
await transaction.validateTrainingReservation?.({ policy, parent: parent.trainingParentSelection });
|
|
61
64
|
await assertAvailableBudget(transaction, chainId, now, policy.limits.maxIterationSpendUsd, policy.limits.maxDailySpendUsd);
|
|
62
65
|
const sealed = await sealLearningBatch(transaction, {
|
|
63
66
|
action: "seal_batch", operationId: input.operationId, batchId: `batch-${contentHash(id)}`,
|
|
@@ -68,11 +71,16 @@ export async function reserveLearningIteration(transaction, input, actorId, now)
|
|
|
68
71
|
pointers.push(...sealed);
|
|
69
72
|
batch = learningRef(await requireLearningResource(transaction, "batch", sealed[0].id));
|
|
70
73
|
for (const item of eligibility.selected) {
|
|
74
|
+
const consumptionId = learningConsumptionId(chainId, item.evidence);
|
|
75
|
+
const prior = await transaction.get("consumption", consumptionId);
|
|
76
|
+
if (prior && prior.exposure !== "unexposed")
|
|
77
|
+
throw new LearningDomainError("learning_evidence_already_reserved", 409);
|
|
71
78
|
const consumption = LearningConsumptionSchema.parse({
|
|
72
|
-
schemaVersion: "openpond.learningConsumption.v1", id:
|
|
79
|
+
schemaVersion: "openpond.learningConsumption.v1", id: consumptionId, revision: (prior?.revision ?? 0) + 1,
|
|
73
80
|
chainId, iterationId: id, evidence: learningRef(item.evidence), decision: learningRef(item.decision), reservedAt: now,
|
|
81
|
+
exposure: "reserved", executionReceipt: null,
|
|
74
82
|
});
|
|
75
|
-
await transaction.put("consumption", consumption, 0, { parentId: chainId });
|
|
83
|
+
await transaction.put("consumption", consumption, prior?.revision ?? 0, { parentId: chainId });
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
const iteration = LearningIterationSchema.parse({
|
|
@@ -29,7 +29,9 @@ export async function latestAcceptedLearningParent(tx, chainId, requireComplete
|
|
|
29
29
|
/** Reservation captures an accepted revision before dispatch or later reviews.
|
|
30
30
|
* Switching the underlying base requires a separately reviewed learning chain. */
|
|
31
31
|
export async function resolveLearningTrainingParent(tx, policy, selection) {
|
|
32
|
-
|
|
32
|
+
// Supervised iterations retrain the reviewed batch from the pinned base.
|
|
33
|
+
// Accepted SFT candidates remain review evidence, never a GRPO checkpoint handoff.
|
|
34
|
+
if (!selection || policy.training.method === "sft")
|
|
33
35
|
return { trainingParent: policy.trainingParent, trainingParentSelection: null };
|
|
34
36
|
const parent = await requireLearningResource(tx, "iteration", selection.iterationId, selection.iterationRevision);
|
|
35
37
|
const sourcePolicy = await requireLearningRelease(tx, "policy", parent.policy);
|
package/dist/learning/service.js
CHANGED
|
@@ -178,6 +178,8 @@ export function createLearningService(repository, options = {}) {
|
|
|
178
178
|
break;
|
|
179
179
|
}
|
|
180
180
|
case "policy": {
|
|
181
|
+
if (input.content.training.replayBatches.length)
|
|
182
|
+
throw new LearningDomainError("learning_replay_not_supported", 422, "Replay requires an executor that materializes exact examples and weights.");
|
|
181
183
|
const definition = await requireLearningRelease(transaction, "definition", input.content.taskDefinition);
|
|
182
184
|
if (!sameLearningRef(definition.rewardBinding, input.content.rewardBinding))
|
|
183
185
|
throw new LearningDomainError("learning_policy_binding_mismatch", 422);
|